|
@@ -16,6 +16,11 @@ namespace ClankerSaddle {
|
|
|
* (the Statum entrypoint fetch never carries the page's query
|
|
* (the Statum entrypoint fetch never carries the page's query
|
|
|
* string to the server, which is why only the first session used
|
|
* string to the server, which is why only the first session used
|
|
|
* to render) and connects the chosen session's SSE stream itself.
|
|
* to render) and connects the chosen session's SSE stream itself.
|
|
|
|
|
+ * Plan 14 B4: rows are NEWEST-first (the dropdown's order) and
|
|
|
|
|
+ * carry `resumed`/`notice` — the resume-info panel above the
|
|
|
|
|
+ * terminal shows the selected session's notice (the opening brief
|
|
|
|
|
+ * framing for the task's first session, the stored resume_notice
|
|
|
|
|
+ * for resumed ones).
|
|
|
*/
|
|
*/
|
|
|
public class TerminalSessionView : Object {
|
|
public class TerminalSessionView : Object {
|
|
|
public int64 id { get; set; }
|
|
public int64 id { get; set; }
|
|
@@ -23,6 +28,14 @@ namespace ClankerSaddle {
|
|
|
public bool selected { get; set; }
|
|
public bool selected { get; set; }
|
|
|
/** This session's raw PTY log exists (renders raw, not typed). */
|
|
/** This session's raw PTY log exists (renders raw, not typed). */
|
|
|
public bool raw { get; set; }
|
|
public bool raw { get; set; }
|
|
|
|
|
+ /** The session began as a resume (a stored resume_notice). */
|
|
|
|
|
+ public bool resumed { get; set; }
|
|
|
|
|
+ /**
|
|
|
|
|
+ * What the agent first received in this session (markdown):
|
|
|
|
|
+ * the opening-brief framing for the task's first session, the
|
|
|
|
|
+ * stored resume_notice for later ones, "" when none.
|
|
|
|
|
+ */
|
|
|
|
|
+ public string notice { get; set; default = ""; }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/** Public data of the `terminal` slot. */
|
|
/** Public data of the `terminal` slot. */
|
|
@@ -56,6 +69,7 @@ namespace ClankerSaddle {
|
|
|
public class TaskTerminalEntrypoint : StatumEntrypoint {
|
|
public class TaskTerminalEntrypoint : StatumEntrypoint {
|
|
|
|
|
|
|
|
private TaskService task_service = Inversion.inject<TaskService>();
|
|
private TaskService task_service = Inversion.inject<TaskService>();
|
|
|
|
|
+ private InvercargillSql.Orm.OrmSession db = Inversion.inject<InvercargillSql.Orm.OrmSession>();
|
|
|
private UserService users = Inversion.inject<UserService>();
|
|
private UserService users = Inversion.inject<UserService>();
|
|
|
|
|
|
|
|
public override async DirectiveBuilder handle() throws GLib.Error {
|
|
public override async DirectiveBuilder handle() throws GLib.Error {
|
|
@@ -113,12 +127,36 @@ namespace ClankerSaddle {
|
|
|
pub_with_session.comment = action_registry.author_private<AddCommentAction, TaskActionPrivate>(sealed);
|
|
pub_with_session.comment = action_registry.author_private<AddCommentAction, TaskActionPrivate>(sealed);
|
|
|
|
|
|
|
|
var session_rows = new Vector<TerminalSessionView>();
|
|
var session_rows = new Vector<TerminalSessionView>();
|
|
|
|
|
+ // Newest first (plan 14 B4): the dropdown leads with the
|
|
|
|
|
+ // latest session. The FIRST session (lowest id) shows the
|
|
|
|
|
+ // opening-brief framing; later ones their stored
|
|
|
|
|
+ // resume_notice.
|
|
|
|
|
+ int64 first_session = 0;
|
|
|
foreach (var session in sessions) {
|
|
foreach (var session in sessions) {
|
|
|
|
|
+ if (first_session == 0 || session.id < first_session) {
|
|
|
|
|
+ first_session = session.id;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ string opening_notice = yield opening_brief_notice(((!)detail).task);
|
|
|
|
|
+ var ordered = new Vector<ClankerSaddle.Data.Session>();
|
|
|
|
|
+ foreach (var session in sessions) {
|
|
|
|
|
+ ordered.add(session);
|
|
|
|
|
+ }
|
|
|
|
|
+ var session_array = ordered.to_array();
|
|
|
|
|
+ for (int i = session_array.length - 1; i >= 0; i--) {
|
|
|
|
|
+ var session = session_array[i];
|
|
|
|
|
+ bool resumed = session.resume_notice != null
|
|
|
|
|
+ && ((!)session.resume_notice).length > 0;
|
|
|
|
|
+ string notice = session.id == first_session
|
|
|
|
|
+ ? opening_notice : (session.resume_notice ?? "");
|
|
|
session_rows.add(new TerminalSessionView() {
|
|
session_rows.add(new TerminalSessionView() {
|
|
|
id = session.id,
|
|
id = session.id,
|
|
|
- label = @"#$(session.id) — $(format_time(session.started_at)) ($(session.outcome ?? "open"))",
|
|
|
|
|
|
|
+ label = @"Session #$(session.id) — started $(format_time(session.started_at))"
|
|
|
|
|
+ + (resumed ? " [resumed]" : ""),
|
|
|
selected = session.id == selected,
|
|
selected = session.id == selected,
|
|
|
- raw = yield task_service.has_raw_log(session.id)
|
|
|
|
|
|
|
+ raw = yield task_service.has_raw_log(session.id),
|
|
|
|
|
+ resumed = resumed,
|
|
|
|
|
+ notice = notice
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -136,5 +174,30 @@ namespace ClankerSaddle {
|
|
|
private static string format_time(DateTime? time) {
|
|
private static string format_time(DateTime? time) {
|
|
|
return time != null ? ((!)time).format("%Y-%m-%d %H:%M") : "";
|
|
return time != null ? ((!)time).format("%Y-%m-%d %H:%M") : "";
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * The opening-brief framing for a task's FIRST session (plan
|
|
|
|
|
+ * 14 B4): what the assignment this session started with asked
|
|
|
|
|
+ * for — the goal's brief for a goal agent's run, the task's
|
|
|
|
|
+ * mandate otherwise (whichever carries text). "" when neither
|
|
|
|
|
+ * does (an empty-state panel).
|
|
|
|
|
+ */
|
|
|
|
|
+ private async string opening_brief_notice(TaskEntity task) throws GLib.Error {
|
|
|
|
|
+ string brief = task.description ?? "";
|
|
|
|
|
+ if (task.kind == TASK_KIND_GOAL_RUN && task.goal_id > 0) {
|
|
|
|
|
+ var goal = yield db.query<Goal>()
|
|
|
|
|
+ .where(Invercargill.Expressions.expr("id == $0",
|
|
|
|
|
+ Invercargill.Expressions.elem<int64?>(task.goal_id)))
|
|
|
|
|
+ .first_async();
|
|
|
|
|
+ if (goal != null && ((!)goal).brief.length > 0) {
|
|
|
|
|
+ brief = ((!)goal).brief;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if (brief.strip().length == 0) {
|
|
|
|
|
+ return "";
|
|
|
|
|
+ }
|
|
|
|
|
+ return "## Opening brief\n\nThe assignment this session started with:\n\n"
|
|
|
|
|
+ + brief;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|