@meistrari/remy-cli 1.15.0 → 1.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +15 -1
  2. package/dist/remy.js +227 -61
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -54,6 +54,8 @@ Published files and Tela Pages appear in the timeline. File artifacts include th
54
54
 
55
55
  When Remy creates a plan, the session shows its checklist in the timeline and keeps `Plan <done>/<total>` with the current item pinned above the composer. Updates change the same checklist instead of producing repeated rows, and an unfinished plan remains visible after reconnecting or between turns. The collapsed timeline shows up to five plan items; press `Ctrl+O` for the complete checklist and activity detail.
56
56
 
57
+ Enter `/compact` to admit a durable main-thread context compaction control without sending a human message. Remy reports request admission immediately and shows the eventual completed, no-op, failed, or unknown provider result in retained activity. Compaction waits for a safe point and does not interrupt active work or jump ahead of an earlier Next message.
58
+
57
59
  Drag to select visible text in any Remy view; Remy copies it to the local clipboard and emits OSC 52 for terminal or remote-session clipboard support, then clears the selection highlight. Published file artifacts show their filename and muted, full web preview URL; open the URL in a browser to select that artifact in its session. Tela Page rows retain their canonical Page URL. Remy does not fetch or print artifact bytes. When the conversation has focus, press `Tab` to return to the composer.
58
60
 
59
61
  | Control | Result |
@@ -61,6 +63,7 @@ Drag to select visible text in any Remy view; Remy copies it to the local clipbo
61
63
  | `Esc` | Interrupt the active turn; the session stays open. |
62
64
  | `/complete` | Complete an open session after its active turn stops. |
63
65
  | `/cancel` | Cancel the current session, including an active turn. |
66
+ | `/compact` | Admit a main-thread context compaction request without sending a message; the provider result appears in retained activity. |
64
67
  | `/sessions` | Return to the dashboard. |
65
68
  | `/new` | Start another new-session flow. |
66
69
  | `/logout` | Sign out of Remy after confirmation. |
@@ -99,6 +102,14 @@ The branch-suggestion flow belongs to the interactive new-session wizard (`remy`
99
102
  remy --session <session-id>
100
103
  ```
101
104
 
105
+ To submit a follow-up immediately, add one quoted positional `prompt`:
106
+
107
+ ```bash
108
+ remy --session <session-id> "Add regression coverage for that fix"
109
+ ```
110
+
111
+ Remy sends the prompt as **Steer**, then opens the conversation. Omitting the prompt only attaches. An explicitly empty prompt is rejected. Use `--` before a prompt beginning with `--` to treat it as text.
112
+
102
113
  ### Automate a session
103
114
 
104
115
  `remy new` and `remy --session` use JSON Lines output automatically when standard input or output is not a terminal. Pass `--no-tui` to choose that mode explicitly; `--json` also selects it.
@@ -108,8 +119,11 @@ Opaque session and message metadata returned by the API is accepted for compatib
108
119
  ```bash
109
120
  remy new --no-tui --repository owner/repository "Add a health check endpoint"
110
121
  remy --session <session-id> --json
122
+ remy --session <session-id> --no-tui "Add regression coverage for that fix"
111
123
  ```
112
124
 
125
+ With a follow-up prompt, Remy submits it once and streams JSON Lines until that message reaches a terminal outcome, even if a previous message is cached locally. It exits with status `0` for a completed turn or `1` for another terminal outcome or an API failure. A follow-up does not emit a `created` record because the session already exists. If the local session cache becomes unavailable after admission, Remy warns once and continues observing the accepted follow-up in memory. Do not resubmit the prompt because of that warning; local resume metadata may remain stale. These commands require prior sign-in and can be used by scripts or other agents.
126
+
113
127
  For a newly created session, Remy writes a `created` record, event-name records as they arrive, then a `terminal` record when the submitted turn settles. A completed turn exits with status `0`; another terminal outcome exits with status `1`.
114
128
 
115
129
  ```json
@@ -151,7 +165,7 @@ remy logout [--api-url <url>]
151
165
  remy whoami
152
166
  remy dashboard
153
167
  remy new [--repository <owner/name> ... --installation <id> --model <name> --reasoning-effort <low|medium|high|xhigh> --attach <path> ... --no-tui --json] <prompt>
154
- remy --session <session-id> [--no-tui] [--json]
168
+ remy --session <session-id> [--no-tui] [--json] [prompt]
155
169
  ```
156
170
 
157
171
  Run `remy <command> --help` for flags and command-specific usage. `remy whoami` prints the saved signed-in identity and organization.
package/dist/remy.js CHANGED
@@ -32214,6 +32214,10 @@ var interruptAgentCommandSchema = runningCommandBaseSchema.extend({
32214
32214
  turnId: zod_default2.string().min(1).optional(),
32215
32215
  reason: zod_default2.string().min(1).optional()
32216
32216
  }).strict();
32217
+ var compactContextAgentCommandSchema = runningCommandBaseSchema.extend({
32218
+ type: zod_default2.literal("agent.compact-context"),
32219
+ commandId: commandIdSchema
32220
+ }).strict();
32217
32221
  var stopAgentCommandSchema = runningCommandBaseSchema.extend({
32218
32222
  type: zod_default2.literal("agent.stop"),
32219
32223
  reason: zod_default2.string().min(1).optional()
@@ -32227,6 +32231,7 @@ var respondUserInputAgentCommandSchema = runningCommandBaseSchema.extend({
32227
32231
  var agentCommandSchema = zod_default2.union([
32228
32232
  sendPromptCommandSchema,
32229
32233
  interruptAgentCommandSchema,
32234
+ compactContextAgentCommandSchema,
32230
32235
  stopAgentCommandSchema,
32231
32236
  respondUserInputAgentCommandSchema
32232
32237
  ]);
@@ -32420,6 +32425,12 @@ var agentTurnEndStatusSchema = zod_default2.enum(["completed", "failed", "cancel
32420
32425
  var agentSubagentStatusSchema = zod_default2.enum(["completed", "failed", "cancelled"]);
32421
32426
  var agentContextCompactionTriggerSchema = zod_default2.enum(["manual", "auto", "unknown"]);
32422
32427
  var agentContextCompactionStatusSchema = zod_default2.enum(["completed", "failed"]);
32428
+ var agentContextCompactionRequestResultSchema = zod_default2.discriminatedUnion("status", [
32429
+ zod_default2.object({ status: zod_default2.literal("completed") }).strict(),
32430
+ zod_default2.object({ status: zod_default2.literal("no_op"), reason: zod_default2.string() }).strict(),
32431
+ zod_default2.object({ status: zod_default2.literal("failed"), error: agentErrorSchema }).strict(),
32432
+ zod_default2.object({ status: zod_default2.literal("unknown") }).strict()
32433
+ ]);
32423
32434
  var agentErrorSourceSchema = zod_default2.enum(["provider", "runtime", "transport", "unknown"]);
32424
32435
  var agentSkillScopeSchema = zod_default2.enum(["user", "repository", "system", "admin"]);
32425
32436
  var agentSkillSchema = zod_default2.object({
@@ -32672,6 +32683,13 @@ var contextCompactionCompletedAgentEventSchema = agentTurnEventBaseSchema.extend
32672
32683
  error: agentErrorSchema.optional()
32673
32684
  }).strict()
32674
32685
  }).strict();
32686
+ var contextCompactionRequestCompletedAgentEventSchema = agentEventBaseSchema.extend({
32687
+ type: zod_default2.literal("context.compaction.request.completed"),
32688
+ payload: zod_default2.object({
32689
+ commandId: zod_default2.string().min(1),
32690
+ result: agentContextCompactionRequestResultSchema
32691
+ }).strict()
32692
+ }).strict();
32675
32693
  var userInputRequestedAgentEventSchema = agentTurnEventBaseSchema.extend({
32676
32694
  type: zod_default2.literal("user-input.requested"),
32677
32695
  payload: zod_default2.object({
@@ -32724,6 +32742,7 @@ var agentEventSchema = zod_default2.discriminatedUnion("type", [
32724
32742
  contextUpdatedAgentEventSchema,
32725
32743
  contextCompactionStartedAgentEventSchema,
32726
32744
  contextCompactionCompletedAgentEventSchema,
32745
+ contextCompactionRequestCompletedAgentEventSchema,
32727
32746
  userInputRequestedAgentEventSchema,
32728
32747
  userInputResolvedAgentEventSchema,
32729
32748
  errorAgentEventSchema
@@ -32912,6 +32931,13 @@ var interruptSessionResponseSchema = exports_external2.strictObject({
32912
32931
  type: exports_external2.literal("agent.interrupt")
32913
32932
  })
32914
32933
  });
32934
+ var compactSessionContextResponseSchema = exports_external2.strictObject({
32935
+ command: exports_external2.strictObject({
32936
+ id: exports_external2.string(),
32937
+ sequence: exports_external2.number().int().positive(),
32938
+ type: exports_external2.literal("agent.compact-context")
32939
+ })
32940
+ });
32915
32941
  var withdrawSessionMessageResponseSchema = exports_external2.strictObject({
32916
32942
  command: exports_external2.strictObject({
32917
32943
  id: exports_external2.string(),
@@ -33082,6 +33108,24 @@ async function interruptSession({
33082
33108
  throw new CodingAgentProtocolError("Session interrupt response did not match the public API contract.", { cause: parsed.error });
33083
33109
  return parsed.data.command;
33084
33110
  }
33111
+ async function compactSessionContext({
33112
+ client,
33113
+ sessionId,
33114
+ idempotencyKey
33115
+ }) {
33116
+ const response = await client.request(`/v1/sessions/${encodeURIComponent(sessionId)}/compact`, {
33117
+ method: "POST",
33118
+ headers: {
33119
+ "content-type": "application/json",
33120
+ "Idempotency-Key": idempotencyKey
33121
+ },
33122
+ body: "{}"
33123
+ });
33124
+ const parsed = compactSessionContextResponseSchema.safeParse(await parseJson2(response, "Session context compaction response was not valid JSON."));
33125
+ if (!parsed.success)
33126
+ throw new CodingAgentProtocolError("Session context compaction response did not match the public API contract.", { cause: parsed.error });
33127
+ return parsed.data.command;
33128
+ }
33085
33129
  async function completeSession({ client, sessionId }) {
33086
33130
  const response = await client.request(`/v1/sessions/${encodeURIComponent(sessionId)}/complete`, { method: "PUT" });
33087
33131
  const parsed = sessionDetailResponseSchema.safeParse(await parseJson2(response, "Session completion response was not valid JSON."));
@@ -34022,6 +34066,9 @@ var wrappedContextCompactionStartedAgentEventSchema = contextCompactionStartedAg
34022
34066
  var wrappedContextCompactionCompletedAgentEventSchema = contextCompactionCompletedAgentEventSchema.extend({
34023
34067
  type: zod_default2.literal("agent.context.compaction.completed")
34024
34068
  }).strict();
34069
+ var wrappedContextCompactionRequestCompletedAgentEventSchema = contextCompactionRequestCompletedAgentEventSchema.extend({
34070
+ type: zod_default2.literal("agent.context.compaction.request.completed")
34071
+ }).strict();
34025
34072
  var wrappedUserInputRequestedAgentEventSchema = userInputRequestedAgentEventSchema.extend({
34026
34073
  type: zod_default2.literal("agent.user-input.requested")
34027
34074
  }).strict();
@@ -34056,11 +34103,31 @@ var wrappedAgentEventSchema = zod_default2.discriminatedUnion("type", [
34056
34103
  wrappedContextUpdatedAgentEventSchema,
34057
34104
  wrappedContextCompactionStartedAgentEventSchema,
34058
34105
  wrappedContextCompactionCompletedAgentEventSchema,
34106
+ wrappedContextCompactionRequestCompletedAgentEventSchema,
34059
34107
  wrappedUserInputRequestedAgentEventSchema,
34060
34108
  wrappedUserInputResolvedAgentEventSchema,
34061
34109
  wrappedErrorAgentEventSchema
34062
34110
  ]);
34063
34111
 
34112
+ // ../../packages/agents-protocol/src/agent-identity.ts
34113
+ function normalizeAgentSubagentIdentity(identity) {
34114
+ return {
34115
+ actorId: identity.actorId,
34116
+ subagentId: identity.subagentId,
34117
+ parentActorId: identity.parentActorId,
34118
+ origin: identity.origin,
34119
+ parentToolCallId: identity.parentToolCallId ?? (identity.origin.type === "tool_call" ? identity.origin.toolCallId : undefined)
34120
+ };
34121
+ }
34122
+ function agentSubagentIdentitiesEqual(left, right) {
34123
+ const normalizedLeft = normalizeAgentSubagentIdentity(left);
34124
+ const normalizedRight = normalizeAgentSubagentIdentity(right);
34125
+ return normalizedLeft.actorId === normalizedRight.actorId && normalizedLeft.subagentId === normalizedRight.subagentId && normalizedLeft.parentActorId === normalizedRight.parentActorId && normalizedLeft.parentToolCallId === normalizedRight.parentToolCallId && (normalizedLeft.origin.type === "tool_call" ? normalizedRight.origin.type === "tool_call" && normalizedLeft.origin.toolCallId === normalizedRight.origin.toolCallId : normalizedRight.origin.type === "provider_task" && normalizedLeft.origin.taskId === normalizedRight.origin.taskId);
34126
+ }
34127
+ function agentRuntimeIdentityKey(event) {
34128
+ return `${event.sessionId.length}:${event.sessionId}:${event.providerSessionId.length}:${event.providerSessionId}`;
34129
+ }
34130
+
34064
34131
  // src/sessions/projection.ts
34065
34132
  var sessionMessageCreatedEventSchema = exports_external2.object({
34066
34133
  type: exports_external2.literal("session.message.created"),
@@ -34389,11 +34456,7 @@ function projectAgentTurnEnded({ state, event, occurredAt, retainedEventId }) {
34389
34456
  retainedTurnEnds: { ...state.retainedTurnEnds, [event.turnId]: { actorType: event.actor.type, outcome: event.payload.status } },
34390
34457
  messageTurns: Object.fromEntries(Object.entries(state.messageTurns).map(([messageId, turn]) => [messageId, turn.turnId === event.turnId ? { ...turn, outcome: event.payload.status } : turn]))
34391
34458
  } : state;
34392
- const card = event.actor.type === "main" ? event.payload.status === "failed" ? { kind: "failure", weight: "signal", title: "Remy turn failed", summary: "Turn failed." } : { kind: "lifecycle", weight: "noise", title: "Remy turn ended", summary: `Turn ${event.payload.status}.` } : createChildActivityCard({
34393
- identity: event.actor,
34394
- activityTitle: event.payload.status === "failed" ? "Turn failed" : "Turn ended",
34395
- card: event.payload.status === "failed" ? { kind: "failure", weight: "signal", summary: providerText(event.payload.error?.message, "Turn failed.") } : { kind: "lifecycle", weight: "noise", summary: `Turn ${event.payload.status}.` }
34396
- });
34459
+ const card = toAgentActivityCard(event);
34397
34460
  return appendActivity({ state: stateWithTurnOutcome, retainedEventId, occurredAt, card });
34398
34461
  }
34399
34462
  function projectDurableAgentEvent({ state, event, occurredAt, retainedEventId }) {
@@ -34538,7 +34601,7 @@ function toMainAgentActivityCard(event) {
34538
34601
  case "agent.turn.started":
34539
34602
  return { kind: "lifecycle", weight: "noise", title: "Remy turn started", summary: "Turn started." };
34540
34603
  case "agent.turn.ended":
34541
- return event.payload.status === "failed" ? { kind: "failure", weight: "signal", title: "Remy turn failed", summary: providerText(event.payload.error?.message, "Turn failed.") } : { kind: "lifecycle", weight: "noise", title: "Remy turn ended", summary: `Turn ${event.payload.status}.` };
34604
+ return event.payload.status === "failed" ? { kind: "failure", weight: "signal", title: "Remy turn failed", summary: "Turn failed." } : { kind: "lifecycle", weight: "noise", title: "Remy turn ended", summary: `Turn ${event.payload.status}.` };
34542
34605
  case "agent.work.observed":
34543
34606
  return { kind: "progress", weight: "noise", title: "Work progress", summary: "Remy updated its work plan.", detail: jsonDetail(event.payload.observations) };
34544
34607
  case "agent.message.started":
@@ -34574,6 +34637,16 @@ function toMainAgentActivityCard(event) {
34574
34637
  return { kind: "reasoning", weight: "noise", title: "Context compaction started", summary: "Remy is compacting context." };
34575
34638
  case "agent.context.compaction.completed":
34576
34639
  return event.payload.status === "failed" ? { kind: "failure", weight: "signal", title: "Context compaction failed", summary: providerText(event.payload.error?.message, "Context compaction failed.") } : { kind: "reasoning", weight: "noise", title: "Context compaction completed", summary: "Remy compacted context." };
34640
+ case "agent.context.compaction.request.completed": {
34641
+ const result = event.payload.result;
34642
+ if (result.status === "completed")
34643
+ return { kind: "progress", weight: "signal", title: "Context compaction completed", summary: "The provider completed manual context compaction." };
34644
+ if (result.status === "no_op")
34645
+ return { kind: "progress", weight: "signal", title: "Context not compacted", summary: providerText(result.reason, "The provider did not compact context.") };
34646
+ if (result.status === "failed")
34647
+ return { kind: "failure", weight: "signal", title: "Context compaction failed", summary: providerText(result.error.message, "Context compaction failed.") };
34648
+ return { kind: "progress", weight: "signal", title: "Context compaction result unknown", summary: "The provider result could not be correlated." };
34649
+ }
34577
34650
  case "agent.user-input.requested":
34578
34651
  return { kind: "approval-question", weight: "signal", title: "Remy needs input", summary: providerText(event.payload.prompt, "Remy needs input."), detail: jsonDetail(event.payload.questions) };
34579
34652
  case "agent.user-input.resolved":
@@ -34659,6 +34732,7 @@ function toChildAgentActivityCard({ event, identity }) {
34659
34732
  case "agent.session.skills.updated":
34660
34733
  case "agent.session.state.changed":
34661
34734
  case "agent.session.ended":
34735
+ case "agent.context.compaction.request.completed":
34662
34736
  case "agent.user-input.resolved":
34663
34737
  case "agent.error":
34664
34738
  throw new SessionProjectionProtocolError(`Cannot attribute non-child session event type ${event.type} to a subagent.`);
@@ -34701,7 +34775,7 @@ ${card.detail}`;
34701
34775
  function childActivityIdentity(event) {
34702
34776
  if (event.type === "agent.subagent.started" || event.type === "agent.subagent.progress" || event.type === "agent.subagent.ended") {
34703
34777
  if (event.actor.type === "subagent") {
34704
- if (!subagentLifecycleIdentityMatches({ actor: event.actor, payload: event.payload }))
34778
+ if (!agentSubagentIdentitiesEqual(event.actor, event.payload))
34705
34779
  throw new SessionProjectionProtocolError(`Retained session event type ${event.type} carried contradictory subagent actor and payload identity.`);
34706
34780
  return {
34707
34781
  ...event.actor,
@@ -34723,14 +34797,6 @@ function childActivityIdentity(event) {
34723
34797
  }
34724
34798
  return;
34725
34799
  }
34726
- function subagentLifecycleIdentityMatches({ actor, payload }) {
34727
- return actor.actorId === payload.actorId && actor.subagentId === payload.subagentId && actor.parentActorId === payload.parentActorId && actor.parentToolCallId === payload.parentToolCallId && subagentOriginsMatch(actor.origin, payload.origin);
34728
- }
34729
- function subagentOriginsMatch(left, right) {
34730
- if (left.type === "tool_call")
34731
- return right.type === "tool_call" && left.toolCallId === right.toolCallId;
34732
- return right.type === "provider_task" && left.taskId === right.taskId;
34733
- }
34734
34800
  function recordAgentLineageEvent({
34735
34801
  state,
34736
34802
  event,
@@ -34746,7 +34812,7 @@ function recordAgentLineageEvent({
34746
34812
  return state;
34747
34813
  throw error93;
34748
34814
  }
34749
- const runtimeBase = lineageRuntimeBase(parsed);
34815
+ const runtimeBase = agentRuntimeIdentityKey(parsed);
34750
34816
  let childLineage = state.childLineage;
34751
34817
  if (parsed.type === "agent.session.started" || parsed.type === "agent.session.ended") {
34752
34818
  const boundaryKey = `${runtimeBase}:${lengthPrefixed(parsed.eventId)}`;
@@ -34805,7 +34871,7 @@ function recordAgentLineageEvent({
34805
34871
  return stampResolvedAttribution({ state: nextState, retainedEventId, fact, owner: resolution.owner });
34806
34872
  }
34807
34873
  function resolveBodyFactAgainstOwners(owners, fact) {
34808
- const candidates = owners.filter((owner) => owner.runtimeScope === fact.runtimeScope && childIdentityMatches(owner.identity, fact.identity));
34874
+ const candidates = owners.filter((owner) => owner.runtimeScope === fact.runtimeScope && agentSubagentIdentitiesEqual(owner.identity, fact.identity));
34809
34875
  const turnCandidates = candidates.filter((owner) => owner.childTurnIds.has(fact.turnId));
34810
34876
  if (turnCandidates.length === 1)
34811
34877
  return { status: "known", owner: turnCandidates[0] };
@@ -34837,9 +34903,6 @@ function stampResolvedAttribution({ state, retainedEventId, fact, owner }) {
34837
34903
  }
34838
34904
  return state;
34839
34905
  }
34840
- function lineageRuntimeBase(event) {
34841
- return `${lengthPrefixed(event.sessionId)}:${lengthPrefixed(event.providerSessionId)}`;
34842
- }
34843
34906
  function lineageRuntimeScope({ runtimeBase, epoch }) {
34844
34907
  return `${runtimeBase}:${epoch}`;
34845
34908
  }
@@ -34852,9 +34915,6 @@ function isChildLineageFact(fact) {
34852
34915
  function isSubagentLifecycleType(type) {
34853
34916
  return type === "agent.subagent.started" || type === "agent.subagent.progress" || type === "agent.subagent.ended";
34854
34917
  }
34855
- function childIdentityMatches(left, right) {
34856
- return left.actorId === right.actorId && left.subagentId === right.subagentId && left.parentActorId === right.parentActorId && left.parentToolCallId === right.parentToolCallId && subagentOriginsMatch(left.origin, right.origin);
34857
- }
34858
34918
  function childIdentityCanAnchor(identity) {
34859
34919
  return identity.actorId !== identity.parentActorId && (identity.origin.type !== "tool_call" || identity.parentToolCallId === undefined || identity.parentToolCallId === identity.origin.toolCallId);
34860
34920
  }
@@ -34888,9 +34948,9 @@ function reconcileChildLineage(state) {
34888
34948
  if (parent && ownerHasActorAncestor(parent, fact.identity.actorId))
34889
34949
  return;
34890
34950
  const samePlacement = owners.filter((owner2) => owner2.runtimeScope === fact.runtimeScope && owner2.owningMainTurnId === owningMainTurnId && owner2.parent === parent && owner2.identity.actorId === fact.identity.actorId && owner2.identity.subagentId === fact.identity.subagentId);
34891
- if (samePlacement.some((owner2) => !childIdentityMatches(owner2.identity, fact.identity)))
34951
+ if (samePlacement.some((owner2) => !agentSubagentIdentitiesEqual(owner2.identity, fact.identity)))
34892
34952
  return;
34893
- const existing = samePlacement.find((owner2) => childIdentityMatches(owner2.identity, fact.identity));
34953
+ const existing = samePlacement.find((owner2) => agentSubagentIdentitiesEqual(owner2.identity, fact.identity));
34894
34954
  if (existing)
34895
34955
  return existing;
34896
34956
  const owner = {
@@ -34924,7 +34984,7 @@ function reconcileChildLineage(state) {
34924
34984
  const toolTurn = mainTurnForToolOrigin(fact);
34925
34985
  if (toolTurn === undefined)
34926
34986
  continue;
34927
- const owner = owners.find((candidate) => candidate.runtimeScope === fact.runtimeScope && candidate.parent === null && candidate.owningMainTurnId === toolTurn && childIdentityMatches(candidate.identity, fact.identity)) ?? createOwner({ fact, owningMainTurnId: toolTurn, parent: null });
34987
+ const owner = owners.find((candidate) => candidate.runtimeScope === fact.runtimeScope && candidate.parent === null && candidate.owningMainTurnId === toolTurn && agentSubagentIdentitiesEqual(candidate.identity, fact.identity)) ?? createOwner({ fact, owningMainTurnId: toolTurn, parent: null });
34928
34988
  if (owner) {
34929
34989
  ownerByFactOrder.set(fact.order, owner);
34930
34990
  owner.childTurnIds.add(fact.turnId);
@@ -34937,7 +34997,7 @@ function reconcileChildLineage(state) {
34937
34997
  for (const fact of childFacts) {
34938
34998
  if (ownerByFactOrder.has(fact.order))
34939
34999
  continue;
34940
- const candidates = owners.filter((owner2) => owner2.runtimeScope === fact.runtimeScope && childIdentityMatches(owner2.identity, fact.identity));
35000
+ const candidates = owners.filter((owner2) => owner2.runtimeScope === fact.runtimeScope && agentSubagentIdentitiesEqual(owner2.identity, fact.identity));
34941
35001
  const turnCandidates = candidates.filter((owner2) => owner2.childTurnIds.has(fact.turnId));
34942
35002
  const owner = turnCandidates.length === 1 ? turnCandidates[0] : candidates.length === 1 ? candidates[0] : undefined;
34943
35003
  if (!owner || fact.eventType === "agent.subagent.started")
@@ -35013,12 +35073,16 @@ function ownerPath(owner) {
35013
35073
  return path;
35014
35074
  }
35015
35075
  function childAttributionEqual(left, right) {
35016
- if (left.status !== right.status || left.activityTitle !== right.activityTitle || !childIdentityMatches(left.identity, right.identity)) {
35076
+ if (left === right)
35077
+ return true;
35078
+ if (!left || !right)
35079
+ return false;
35080
+ if (left.status !== right.status || left.activityTitle !== right.activityTitle || left.identity.name !== right.identity.name || !agentSubagentIdentitiesEqual(left.identity, right.identity)) {
35017
35081
  return false;
35018
35082
  }
35019
35083
  if (left.status === "unresolved" || right.status === "unresolved")
35020
35084
  return true;
35021
- return left.ownerKey === right.ownerKey && left.owningMainTurnId === right.owningMainTurnId && left.path.length === right.path.length && left.path.every((identity, index) => childIdentityMatches(identity, right.path[index]));
35085
+ return left.ownerKey === right.ownerKey && left.owningMainTurnId === right.owningMainTurnId && left.path.length === right.path.length && left.path.every((identity, index) => identity.name === right.path[index].name && agentSubagentIdentitiesEqual(identity, right.path[index]));
35022
35086
  }
35023
35087
  function terminalSafeSingleLine(value) {
35024
35088
  return stripAnsi(value).replace(/\s+/gu, " ").trim();
@@ -35251,6 +35315,7 @@ function createRemoteSessionController(dependencies) {
35251
35315
  let stopped = false;
35252
35316
  let state;
35253
35317
  let cachedLastRetainedEventId;
35318
+ let cacheUnavailable = false;
35254
35319
  let reasoningPreviewState = initialReasoningPreviewState();
35255
35320
  let reasoningConnectionGeneration = 0;
35256
35321
  let reasoningHistorySequenceFloor = -1;
@@ -35259,7 +35324,10 @@ function createRemoteSessionController(dependencies) {
35259
35324
  ready = new Promise((resolve) => {
35260
35325
  resolveReady = resolve;
35261
35326
  });
35262
- const cache = await readSessionCache(cachePath);
35327
+ const cache = await readSessionCache(cachePath).catch((error93) => {
35328
+ handleCacheError(error93);
35329
+ return null;
35330
+ });
35263
35331
  cachedLastRetainedEventId = input.mode === "live" ? input.lastRetainedEventId ?? cache?.lastRetainedEventId : undefined;
35264
35332
  state = createSessionViewState({
35265
35333
  detail: input.detail,
@@ -35486,6 +35554,8 @@ function createRemoteSessionController(dependencies) {
35486
35554
  };
35487
35555
  }
35488
35556
  async function writeCache() {
35557
+ if (cacheUnavailable)
35558
+ return;
35489
35559
  const currentState = getState();
35490
35560
  const cache = {
35491
35561
  version: 1,
@@ -35495,7 +35565,13 @@ function createRemoteSessionController(dependencies) {
35495
35565
  ...currentState.activeMessageId ? { activeMessageId: currentState.activeMessageId } : {},
35496
35566
  updatedAt: new Date().toISOString()
35497
35567
  };
35498
- await writeSessionCache({ path: cachePath, cache });
35568
+ await writeSessionCache({ path: cachePath, cache }).catch(handleCacheError);
35569
+ }
35570
+ function handleCacheError(error93) {
35571
+ if (!dependencies.onCacheError)
35572
+ throw error93;
35573
+ cacheUnavailable = true;
35574
+ dependencies.onCacheError(error93);
35499
35575
  }
35500
35576
  function publishState(frame) {
35501
35577
  const update = { state: getState(), ...frame ? { frame } : {} };
@@ -37990,6 +38066,7 @@ var composerSlashCommands = [
37990
38066
  { value: "/sessions", description: "Back to the session list" },
37991
38067
  { value: "/complete", description: "Finish this session" },
37992
38068
  { value: "/cancel", description: "Cancel this session" },
38069
+ { value: "/compact", description: "Compact main-thread context" },
37993
38070
  { value: "/new", description: "Start a new session" },
37994
38071
  { value: "/logout", description: "Sign out of Remy" },
37995
38072
  { value: "/help", description: "Show what you can do here" },
@@ -38001,6 +38078,7 @@ async function createSessionTui({
38001
38078
  controller,
38002
38079
  submitMessage,
38003
38080
  requestInterrupt,
38081
+ requestCompact,
38004
38082
  requestComplete,
38005
38083
  requestCancel,
38006
38084
  repositoryLabel,
@@ -38242,6 +38320,7 @@ async function createSessionTui({
38242
38320
  let admittedSubmissions = [];
38243
38321
  let stopState = { kind: "idle" };
38244
38322
  let lifecycleRequestState = { kind: "idle" };
38323
+ let contextCompactionRequestState = { kind: "idle" };
38245
38324
  let helpVisible = false;
38246
38325
  let logoutConfirmationOpen = false;
38247
38326
  let latestState = controller.getState();
@@ -38592,6 +38671,12 @@ async function createSessionTui({
38592
38671
  handleLifecycleRequest("cancel");
38593
38672
  return true;
38594
38673
  }
38674
+ if (command === "/compact") {
38675
+ composer.setText("");
38676
+ composerDraft = { ...composerDraft, text: "" };
38677
+ await handleCompactionRequest();
38678
+ return true;
38679
+ }
38595
38680
  if (command === "/logout") {
38596
38681
  composer.setText("");
38597
38682
  composerDraft = { ...composerDraft, text: "" };
@@ -38679,6 +38764,39 @@ async function createSessionTui({
38679
38764
  }
38680
38765
  render();
38681
38766
  }
38767
+ async function handleCompactionRequest() {
38768
+ if (contextCompactionRequestState.kind === "requesting")
38769
+ return;
38770
+ if (latestState.aggregateStatus !== "open") {
38771
+ composerFeedback = "This session is already terminal.";
38772
+ render();
38773
+ return;
38774
+ }
38775
+ const idempotencyKey = contextCompactionRequestState.kind === "delivery-unknown" ? contextCompactionRequestState.idempotencyKey : crypto.randomUUID();
38776
+ contextCompactionRequestState = { kind: "requesting", idempotencyKey };
38777
+ composerFeedback = "Requesting context compaction\u2026";
38778
+ render();
38779
+ try {
38780
+ await requestCompact({ idempotencyKey });
38781
+ if (!destroyed) {
38782
+ contextCompactionRequestState = { kind: "admitted" };
38783
+ composerFeedback = "Context compaction request admitted. Result will appear in the timeline.";
38784
+ }
38785
+ } catch (error93) {
38786
+ if (!destroyed) {
38787
+ const message = error93 instanceof Error ? error93.message : String(error93);
38788
+ if (isKnownClientRejection(error93)) {
38789
+ contextCompactionRequestState = { kind: "idle" };
38790
+ composerFeedback = `Could not request context compaction: ${message}`;
38791
+ } else {
38792
+ contextCompactionRequestState = { kind: "delivery-unknown", idempotencyKey };
38793
+ composerFeedback = `Context compaction admission is unknown: ${message}. Run /compact again to retry the same request.`;
38794
+ }
38795
+ }
38796
+ }
38797
+ if (!destroyed)
38798
+ render();
38799
+ }
38682
38800
  async function handleLifecycleRequest(operation) {
38683
38801
  if (lifecycleRequestState.kind === "pending")
38684
38802
  return;
@@ -38805,6 +38923,7 @@ var helpEntries = [
38805
38923
  { group: "command", token: "/sessions", description: "back to the session list" },
38806
38924
  { group: "command", token: "/complete", description: "finish this session" },
38807
38925
  { group: "command", token: "/cancel", description: "cancel this session" },
38926
+ { group: "command", token: "/compact", description: "compact main-thread context" },
38808
38927
  { group: "command", token: "/logout", description: "sign out of Remy" },
38809
38928
  { group: "command", token: "/exit", description: "leave Remy" },
38810
38929
  { group: "key", token: "esc", description: "stop Remy\u2019s current turn" },
@@ -39022,19 +39141,7 @@ function activityGlyph({ kind, inFlight }) {
39022
39141
  return { glyph: "\u2713", color: PALETTE.dimText };
39023
39142
  }
39024
39143
  function activityCardsHaveEqualDisclosure(left, right) {
39025
- return left.kind === right.kind && left.title === right.title && left.summary === right.summary && left.detail === right.detail && left.detailFormat === right.detailFormat && canonicalValuesEqual(left.attribution, right.attribution);
39026
- }
39027
- function canonicalValuesEqual(left, right) {
39028
- if (Object.is(left, right))
39029
- return true;
39030
- if (Array.isArray(left) || Array.isArray(right)) {
39031
- return Array.isArray(left) && Array.isArray(right) && left.length === right.length && left.every((value, index) => canonicalValuesEqual(value, right[index]));
39032
- }
39033
- if (typeof left !== "object" || left === null || typeof right !== "object" || right === null)
39034
- return false;
39035
- const leftEntries = Object.entries(left);
39036
- const rightEntries = Object.entries(right);
39037
- return leftEntries.length === rightEntries.length && leftEntries.every(([key, value], index) => rightEntries[index]?.[0] === key && canonicalValuesEqual(value, rightEntries[index]?.[1]));
39144
+ return left.kind === right.kind && left.title === right.title && left.summary === right.summary && left.detail === right.detail && left.detailFormat === right.detailFormat && childAttributionEqual(left.attribution, right.attribution);
39038
39145
  }
39039
39146
  function renderActivityGroup({ items, activityExpanded }) {
39040
39147
  if (items.length === 0)
@@ -39235,6 +39342,12 @@ function renderComposerStatus({ admittedSubmissions }) {
39235
39342
 
39236
39343
  `);
39237
39344
  }
39345
+ function isKnownClientRejection(error93) {
39346
+ if (typeof error93 !== "object" || error93 === null || !("status" in error93))
39347
+ return false;
39348
+ const status = error93.status;
39349
+ return typeof status === "number" && status >= 400 && status < 500;
39350
+ }
39238
39351
  async function createDefaultRenderer3() {
39239
39352
  return await createRemyRenderer();
39240
39353
  }
@@ -39495,7 +39608,7 @@ var compactMarkRows = 9;
39495
39608
  var compactMinWidth = 48;
39496
39609
  var compactMinHeight = 20;
39497
39610
  var markBrightnessGain = 4.2;
39498
- var remyCliVersion = "1.15.0";
39611
+ var remyCliVersion = "1.17.0";
39499
39612
  async function showRemySplash({
39500
39613
  createRenderer = createRemyRenderer,
39501
39614
  durationMs = splashDurationMs,
@@ -39868,7 +39981,7 @@ async function dispatchCliCommandWithShutdown({
39868
39981
  });
39869
39982
  }
39870
39983
  if (command.name === "session")
39871
- return await attachSession({ dependencies, sessionId: command.sessionId, noTui: command.noTui, json: command.json });
39984
+ return await attachSession({ dependencies, sessionId: command.sessionId, noTui: command.noTui, json: command.json, prompt: command.prompt });
39872
39985
  return await createNewSession({ dependencies, command });
39873
39986
  }
39874
39987
  function createCliShutdown({ abortSignal }) {
@@ -40775,12 +40888,30 @@ async function attachSession({
40775
40888
  dependencies,
40776
40889
  sessionId,
40777
40890
  noTui,
40778
- json: json3
40891
+ json: json3,
40892
+ prompt
40779
40893
  }) {
40780
40894
  const operations = await createSessionOperations(dependencies);
40781
40895
  const detail = await operations.getSession({ client: operations.client, sessionId });
40782
40896
  const repositories = detail.repositories.map((repository) => ({ id: repository.id, fullName: repository.full_name }));
40783
40897
  const cache = await (dependencies.readSessionCache ?? readSessionCache)(resolveSessionCachePathForCommand({ dependencies, sessionId }));
40898
+ let activeMessageId = cache?.activeMessageId;
40899
+ let cacheWarningReported = false;
40900
+ const onCacheError = prompt === undefined ? undefined : () => {
40901
+ if (cacheWarningReported)
40902
+ return;
40903
+ cacheWarningReported = true;
40904
+ dependencies.output.writeStderr(`Local session cache is unavailable; continuing to observe the accepted follow-up. Do not resubmit the prompt.
40905
+ `);
40906
+ };
40907
+ if (prompt !== undefined) {
40908
+ throwIfAborted2(dependencies.abortSignal);
40909
+ const appended = await operations.appendSessionMessage({
40910
+ client: operations.client,
40911
+ input: { sessionId, text: prompt, fileIds: [], mode: "steer", idempotencyKey: randomUUID5() }
40912
+ });
40913
+ activeMessageId = appended.message.id;
40914
+ }
40784
40915
  await (dependencies.writeSessionCache ?? writeSessionCache)({
40785
40916
  path: resolveSessionCachePathForCommand({ dependencies, sessionId }),
40786
40917
  cache: {
@@ -40788,20 +40919,25 @@ async function attachSession({
40788
40919
  sessionId,
40789
40920
  repositories,
40790
40921
  ...cache?.lastRetainedEventId ? { lastRetainedEventId: cache.lastRetainedEventId } : {},
40791
- ...cache?.activeMessageId ? { activeMessageId: cache.activeMessageId } : {},
40922
+ ...activeMessageId ? { activeMessageId } : {},
40792
40923
  updatedAt: new Date().toISOString()
40793
40924
  }
40925
+ }).catch((error93) => {
40926
+ if (!onCacheError)
40927
+ throw error93;
40928
+ onCacheError();
40794
40929
  });
40795
40930
  return await runAttachedSession({
40796
40931
  dependencies,
40797
40932
  operations,
40798
40933
  sessionId,
40799
40934
  repositories,
40800
- activeMessageId: cache?.activeMessageId,
40935
+ activeMessageId,
40801
40936
  start: { mode: "cold-resume", detail },
40802
40937
  noTui,
40803
40938
  json: json3,
40804
- emitCreated: false
40939
+ emitCreated: false,
40940
+ onCacheError
40805
40941
  });
40806
40942
  }
40807
40943
  async function runAttachedSession({
@@ -40813,7 +40949,8 @@ async function runAttachedSession({
40813
40949
  start,
40814
40950
  noTui,
40815
40951
  json: json3,
40816
- emitCreated
40952
+ emitCreated,
40953
+ onCacheError
40817
40954
  }) {
40818
40955
  if (dependencies.abortSignal?.aborted)
40819
40956
  throw dependencies.abortSignal.reason ?? new Error("interrupted");
@@ -40823,6 +40960,7 @@ async function runAttachedSession({
40823
40960
  repositories,
40824
40961
  ...activeMessageId ? { activeMessageId } : {},
40825
40962
  environment: dependencies.environment,
40963
+ onCacheError,
40826
40964
  getSession: async ({ sessionId: id }) => await operations.getSession({ client: operations.client, sessionId: id }),
40827
40965
  listSessionEvents: async ({ sessionId: id, limit, after, signal }) => await operations.listSessionEvents({ client: operations.client, sessionId: id, limit, after, signal }),
40828
40966
  openEventStream: ({ sessionId: id, lastRetainedEventId, signal, onSynchronized }) => operations.streamSessionEvents({ client: operations.client, sessionId: id, lastRetainedEventId, signal, onSynchronized })
@@ -40898,6 +41036,9 @@ async function runAttachedSession({
40898
41036
  requestInterrupt: async ({ idempotencyKey }) => {
40899
41037
  await operations.interruptSession({ client: operations.client, sessionId, idempotencyKey });
40900
41038
  },
41039
+ requestCompact: async ({ idempotencyKey }) => {
41040
+ await operations.compactSessionContext({ client: operations.client, sessionId, idempotencyKey });
41041
+ },
40901
41042
  requestComplete: async () => {
40902
41043
  const detail = await operations.completeSession({ client: operations.client, sessionId });
40903
41044
  controller.updateDetail(detail);
@@ -41024,6 +41165,7 @@ async function createSessionOperations(dependencies, { onAuthenticated } = {}) {
41024
41165
  appendSessionMessage: dependencies.appendSessionMessage ?? appendSessionMessage,
41025
41166
  cancelSession: dependencies.cancelSession ?? cancelSession,
41026
41167
  completeSession: dependencies.completeSession ?? completeSession,
41168
+ compactSessionContext: dependencies.compactSessionContext ?? compactSessionContext,
41027
41169
  interruptSession: dependencies.interruptSession ?? interruptSession,
41028
41170
  getSession: dependencies.getSession ?? getSession,
41029
41171
  listSessionEvents: dependencies.listSessionEvents ?? listSessionEvents,
@@ -41044,6 +41186,7 @@ async function createSessionOperations(dependencies, { onAuthenticated } = {}) {
41044
41186
  appendSessionMessage: dependencies.appendSessionMessage ?? appendSessionMessage,
41045
41187
  cancelSession: dependencies.cancelSession ?? cancelSession,
41046
41188
  completeSession: dependencies.completeSession ?? completeSession,
41189
+ compactSessionContext: dependencies.compactSessionContext ?? compactSessionContext,
41047
41190
  interruptSession: dependencies.interruptSession ?? interruptSession,
41048
41191
  getSession: dependencies.getSession ?? getSession,
41049
41192
  listSessionEvents: dependencies.listSessionEvents ?? listSessionEvents,
@@ -41200,10 +41343,15 @@ Options:
41200
41343
  `;
41201
41344
  }
41202
41345
  if (topic === "session") {
41203
- return `Usage: remy --session <session-id> [options]
41346
+ return `Usage: remy --session <session-id> [options] [prompt]
41204
41347
 
41205
41348
  Attach to an existing remote session. An interactive terminal opens the session view; --no-tui streams output instead.
41206
41349
 
41350
+ Arguments:
41351
+ prompt Submit a follow-up as Steer before attaching
41352
+
41353
+ With a prompt, JSON output waits for that message's turn result. Use -- before a prompt beginning with --.
41354
+
41207
41355
  Options:
41208
41356
  --no-tui Do not open the interactive terminal view
41209
41357
  --json Write session updates as JSON
@@ -41235,8 +41383,8 @@ Usage:
41235
41383
  Commands:
41236
41384
  remy Open interactive dashboard
41237
41385
  remy dashboard Open interactive dashboard
41238
- remy --session <session-id> [--no-tui] [--json]
41239
- Attach to a session
41386
+ remy --session <session-id> [--no-tui] [--json] [prompt]
41387
+ Attach to a session, optionally submitting a follow-up
41240
41388
  remy new [--repository <owner/name> ... --installation <id> --model <name> --reasoning-effort <low|medium|high|xhigh> --attach <path> ... --no-tui --json] <prompt>
41241
41389
  Create a session
41242
41390
  remy login [--env production|staging] [--api-url <url> --auth-api-url <url> --requester-application-id <uuid> --target-application-id <uuid>]
@@ -41268,14 +41416,32 @@ function parseSessionAttach(argv) {
41268
41416
  const sessionId = argv[0];
41269
41417
  if (!sessionId || sessionId.startsWith("--"))
41270
41418
  throw new Error("--session requires a session ID.");
41271
- const flags = parseFlags(argv.slice(1));
41272
- if (typeof flags["no-tui"] === "string" || typeof flags.json === "string")
41273
- throw new Error("--no-tui and --json do not accept values.");
41274
- for (const flag of Object.keys(flags)) {
41275
- if (flag !== "no-tui" && flag !== "json")
41276
- throw new Error(`Unknown --session flag --${flag}.`);
41419
+ let noTui = false;
41420
+ let json3 = false;
41421
+ let prompt;
41422
+ let positionalOnly = false;
41423
+ for (const token of argv.slice(1)) {
41424
+ if (!positionalOnly && token === "--") {
41425
+ positionalOnly = true;
41426
+ continue;
41427
+ }
41428
+ if (!positionalOnly && token === "--no-tui") {
41429
+ noTui = true;
41430
+ continue;
41431
+ }
41432
+ if (!positionalOnly && token === "--json") {
41433
+ json3 = true;
41434
+ continue;
41435
+ }
41436
+ if (!positionalOnly && token.startsWith("--"))
41437
+ throw new Error(`Unknown --session flag ${token}.`);
41438
+ if (prompt !== undefined)
41439
+ throw new Error("--session accepts one prompt. Quote the full prompt as a single argument.");
41440
+ if (!token.trim())
41441
+ throw new Error("A follow-up prompt must not be empty.");
41442
+ prompt = token;
41277
41443
  }
41278
- return { name: "session", sessionId, noTui: flags["no-tui"] === true, json: flags.json === true };
41444
+ return { name: "session", sessionId, noTui, json: json3, ...prompt === undefined ? {} : { prompt } };
41279
41445
  }
41280
41446
  function parseNewCommand(argv) {
41281
41447
  const repositories = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meistrari/remy-cli",
3
- "version": "1.15.0",
3
+ "version": "1.17.0",
4
4
  "description": "Remy, the Coding Agent terminal client.",
5
5
  "type": "module",
6
6
  "bin": {