Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,32 @@ class ApiClient(
suspend fun agentRun(runUuid: String): AgentRunInfo =
get("/agent/runs/${enc(runUuid)}", DataEnvelope.serializer(AgentRunInfo.serializer())).data

/* Modèles d'agent : catalogue, lancement en un bouton, carte live, réveil du bot Grok. */
suspend fun playbooks(): List<Playbook> =
get("/playbooks", DataEnvelope.serializer(ListSerializer(Playbook.serializer()))).data

suspend fun runPlaybook(projectUuid: String, playbookId: String, goal: String?): PlaybookStarted =
send(
"POST",
"/projects/${enc(projectUuid)}/playbooks/${enc(playbookId)}/run",
buildJsonObject { goal?.trim()?.takeIf { it.isNotEmpty() }?.let { put("goal", it) } },
DataEnvelope.serializer(PlaybookStarted.serializer()),
).data

suspend fun runLive(runUuid: String): RunLive =
get("/agent/runs/${enc(runUuid)}/live", DataEnvelope.serializer(RunLive.serializer())).data

suspend fun wakeGrokBot(projectUuid: String, runUuid: String?): GrokWakeResult =
send(
"POST",
"/grok-bot/wake",
buildJsonObject {
put("project_uuid", projectUuid)
runUuid?.let { put("run_uuid", it) }
},
DataEnvelope.serializer(GrokWakeResult.serializer()),
).data

/** Bail SQL : `pause` | `resume` | `retry`. Refus (409) → exception avec le message serveur. */
suspend fun controlRun(runUuid: String, action: String): RunCancel =
send("POST", "/agent/runs/${enc(runUuid)}/${enc(action)}", buildJsonObject { }, DataEnvelope.serializer(RunCancel.serializer())).data
Expand Down
126 changes: 126 additions & 0 deletions apps/android/app/src/main/java/app/jeser/devforge/data/Playbooks.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package app.jeser.devforge.data

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

/* ---------------- Modèles d'agent (playbooks) et carte live d'un run ---------------- */

@Serializable
data class Playbook(
val id: String,
val slash: String = "",
val name: String = "",
val description: String = "",
val steps: List<String> = emptyList(),
val rule: String = "",
val visual: String = "",
@SerialName("goal_label") val goalLabel: String = "",
@SerialName("goal_placeholder") val goalPlaceholder: String = "",
@SerialName("uses_workers") val usesWorkers: Boolean = false,
)

@Serializable
data class PlaybookStarted(
@SerialName("run_uuid") val runUuid: String,
@SerialName("agent_uuid") val agentUuid: String? = null,
val playbook: String? = null,
val title: String? = null,
)

@Serializable
data class PlaybookRef(val id: String = "", val slash: String = "", val name: String = "", val rule: String = "", val visual: String = "")

@Serializable
data class LiveStep(val n: Int = 0, val label: String = "", val state: String = "pending")

@Serializable
data class LiveWorker(
@SerialName("run_uuid") val runUuid: String,
val title: String? = null,
@SerialName("agent_name") val agentName: String? = null,
val status: String = "queued",
val live: Boolean = false,
val summary: String? = null,
val error: String? = null,
)

@Serializable
data class LiveCounters(
@SerialName("tools_ok") val toolsOk: Int = 0,
@SerialName("tools_failed") val toolsFailed: Int = 0,
@SerialName("workers_total") val workersTotal: Int = 0,
@SerialName("workers_done") val workersDone: Int = 0,
@SerialName("workers_failed") val workersFailed: Int = 0,
val decisions: Int = 0,
)

/** Une ligne « Montre-moi ton travail » : quoi, pourquoi, preuve (lien ou chemin), issue. */
@Serializable
data class RunDecision(
val what: String = "",
val why: String = "",
val evidence: String = "",
val outcome: String = "unknown",
val step: Int? = null,
)

@Serializable
data class RunLive(
@SerialName("run_uuid") val runUuid: String,
@SerialName("project_uuid") val projectUuid: String = "",
val status: String = "running",
val live: Boolean = false,
val verdict: String = "running",
@SerialName("status_text") val statusText: String = "",
val title: String? = null,
val error: String? = null,
val playbook: PlaybookRef? = null,
val steps: List<LiveStep> = emptyList(),
val workers: List<LiveWorker> = emptyList(),
val counters: LiveCounters = LiveCounters(),
val decisions: List<RunDecision> = emptyList(),
)

@Serializable
data class GrokWakeResult(val ok: Boolean = false, val status: Int? = null, val message: String = "", val event: String = "")

/** Libellé court d'une étape (même vocabulaire que le site). */
fun stepStateLabel(state: String): String = when (state) {
"ok" -> "OK"
"failed" -> "Échec"
"running" -> "En cours"
"missing" -> "Sans résultat"
"skipped" -> "Ignorée"
else -> "À venir"
}

/** État d'un worker ramené au vocabulaire des étapes. */
fun workerStepState(status: String): String = when (status) {
"completed" -> "ok"
"failed", "cancelled" -> "failed"
"running" -> "running"
else -> "pending"
}

fun verdictLabel(v: String): String = when (v) {
"ok" -> "Réussi"
"failed" -> "Échec"
"incomplete" -> "Incomplet"
else -> "En cours"
}

/** Pastilles compteurs : seules les valeurs utiles. */
fun livePills(c: LiveCounters): List<String> = buildList {
if (c.workersTotal > 0) add("Workers ${c.workersDone}/${c.workersTotal}")
add("${c.toolsOk} action${if (c.toolsOk > 1) "s" else ""} OK")
if (c.toolsFailed > 0) add("${c.toolsFailed} échec${if (c.toolsFailed > 1) "s" else ""}")
add("${c.decisions} décision${if (c.decisions > 1) "s" else ""}")
}

/** Preuve ouvrable dans le navigateur : http(s) uniquement. Un chemin reste du texte. */
fun evidenceUrl(evidence: String): String? {
val e = evidence.trim()
return if (Regex("^https?://[^\\s/]", RegexOption.IGNORE_CASE).containsMatchIn(e) && e.none { it.isWhitespace() }) e else null
}

val RunLive.needsHelp: Boolean get() = verdict == "failed" || verdict == "incomplete"
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,10 @@ private fun ProjectRoute(graph: AppGraph, uuid: String, onBack: (() -> Unit)?, o
onUrlOpened = vm::urlOpened,
onOpenDraft = vm::openDraft,
onOpenCorrections = vm::openCorrections,
onOpenPlaybooks = vm::openPlaybooks,
onClosePlaybooks = vm::closePlaybooks,
onRunPlaybook = vm::runPlaybook,
onWakeGrok = vm::wakeGrok,
onApplyCorrection = vm::applyCorrection,
onRejectCorrection = vm::rejectCorrection,
onRedoCorrection = vm::redoCorrection,
Expand Down
Loading
Loading