crewly 1.20.206 → 1.20.208
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.
- package/config/hooks/credential-guard/guard.sh +243 -0
- package/dist/backend/backend/src/constants.d.ts +63 -2
- package/dist/backend/backend/src/constants.d.ts.map +1 -1
- package/dist/backend/backend/src/constants.js +67 -1
- package/dist/backend/backend/src/constants.js.map +1 -1
- package/dist/backend/backend/src/controllers/agent-hooks/agent-hooks.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/agent-hooks/agent-hooks.controller.js +17 -1
- package/dist/backend/backend/src/controllers/agent-hooks/agent-hooks.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/cloud/cloud.controller.d.ts +19 -5
- package/dist/backend/backend/src/controllers/cloud/cloud.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/cloud/cloud.controller.js +71 -5
- package/dist/backend/backend/src/controllers/cloud/cloud.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/cloud/cloud.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/cloud/cloud.routes.js +4 -1
- package/dist/backend/backend/src/controllers/cloud/cloud.routes.js.map +1 -1
- package/dist/backend/backend/src/controllers/monitoring/terminal.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/monitoring/terminal.controller.js +4 -0
- package/dist/backend/backend/src/controllers/monitoring/terminal.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/session/session.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/session/session.controller.js +2 -0
- package/dist/backend/backend/src/controllers/session/session.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/workspace/workspace.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/workspace/workspace.routes.js +10 -2
- package/dist/backend/backend/src/controllers/workspace/workspace.routes.js.map +1 -1
- package/dist/backend/backend/src/index.d.ts +8 -0
- package/dist/backend/backend/src/index.d.ts.map +1 -1
- package/dist/backend/backend/src/index.js +54 -2
- package/dist/backend/backend/src/index.js.map +1 -1
- package/dist/backend/backend/src/services/agent/agent-registration.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/agent-registration.service.js +16 -2
- package/dist/backend/backend/src/services/agent/agent-registration.service.js.map +1 -1
- package/dist/backend/backend/src/services/agent/codex-daemon.utils.d.ts +16 -0
- package/dist/backend/backend/src/services/agent/codex-daemon.utils.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/codex-daemon.utils.js +44 -0
- package/dist/backend/backend/src/services/agent/codex-daemon.utils.js.map +1 -1
- package/dist/backend/backend/src/services/agent/control-plane-guard.service.d.ts +14 -2
- package/dist/backend/backend/src/services/agent/control-plane-guard.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/control-plane-guard.service.js +18 -3
- package/dist/backend/backend/src/services/agent/control-plane-guard.service.js.map +1 -1
- package/dist/backend/backend/src/services/agent/credential-guard.service.d.ts +153 -0
- package/dist/backend/backend/src/services/agent/credential-guard.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/agent/credential-guard.service.js +314 -0
- package/dist/backend/backend/src/services/agent/credential-guard.service.js.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.js +5 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.js.map +1 -1
- package/dist/backend/backend/src/services/agent/runtime-agent.service.abstract.d.ts +39 -0
- package/dist/backend/backend/src/services/agent/runtime-agent.service.abstract.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/runtime-agent.service.abstract.js +132 -3
- package/dist/backend/backend/src/services/agent/runtime-agent.service.abstract.js.map +1 -1
- package/dist/backend/backend/src/services/core/credential-files.d.ts +26 -0
- package/dist/backend/backend/src/services/core/credential-files.d.ts.map +1 -0
- package/dist/backend/backend/src/services/core/credential-files.js +60 -0
- package/dist/backend/backend/src/services/core/credential-files.js.map +1 -0
- package/dist/backend/backend/src/services/google/docs.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/google/docs.service.js +14 -1
- package/dist/backend/backend/src/services/google/docs.service.js.map +1 -1
- package/dist/backend/backend/src/services/google/drive.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/google/drive.service.js +6 -4
- package/dist/backend/backend/src/services/google/drive.service.js.map +1 -1
- package/dist/backend/backend/src/services/google/google-api.client.d.ts +8 -3
- package/dist/backend/backend/src/services/google/google-api.client.d.ts.map +1 -1
- package/dist/backend/backend/src/services/google/google-api.client.js +5 -2
- package/dist/backend/backend/src/services/google/google-api.client.js.map +1 -1
- package/dist/backend/backend/src/services/messaging/input-blocked-retry.service.d.ts +10 -0
- package/dist/backend/backend/src/services/messaging/input-blocked-retry.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/messaging/input-blocked-retry.service.js +16 -0
- package/dist/backend/backend/src/services/messaging/input-blocked-retry.service.js.map +1 -1
- package/dist/backend/backend/src/services/messaging/sub-agent-message-queue.service.d.ts +10 -0
- package/dist/backend/backend/src/services/messaging/sub-agent-message-queue.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/messaging/sub-agent-message-queue.service.js +21 -0
- package/dist/backend/backend/src/services/messaging/sub-agent-message-queue.service.js.map +1 -1
- package/dist/backend/backend/src/services/monitoring/credential-guard-alerts.d.ts +68 -0
- package/dist/backend/backend/src/services/monitoring/credential-guard-alerts.d.ts.map +1 -0
- package/dist/backend/backend/src/services/monitoring/credential-guard-alerts.js +95 -0
- package/dist/backend/backend/src/services/monitoring/credential-guard-alerts.js.map +1 -0
- package/dist/backend/backend/src/services/session/input-ledger.d.ts +115 -0
- package/dist/backend/backend/src/services/session/input-ledger.d.ts.map +1 -0
- package/dist/backend/backend/src/services/session/input-ledger.js +144 -0
- package/dist/backend/backend/src/services/session/input-ledger.js.map +1 -0
- package/dist/backend/backend/src/services/session/session-command-helper.d.ts +26 -0
- package/dist/backend/backend/src/services/session/session-command-helper.d.ts.map +1 -1
- package/dist/backend/backend/src/services/session/session-command-helper.js +156 -37
- package/dist/backend/backend/src/services/session/session-command-helper.js.map +1 -1
- package/dist/backend/backend/src/services/session/tui-input-guard.d.ts +40 -0
- package/dist/backend/backend/src/services/session/tui-input-guard.d.ts.map +1 -1
- package/dist/backend/backend/src/services/session/tui-input-guard.js +95 -0
- package/dist/backend/backend/src/services/session/tui-input-guard.js.map +1 -1
- package/dist/backend/backend/src/websocket/terminal.gateway.d.ts.map +1 -1
- package/dist/backend/backend/src/websocket/terminal.gateway.js +4 -1
- package/dist/backend/backend/src/websocket/terminal.gateway.js.map +1 -1
- package/dist/backend/build-info.json +2 -2
- package/dist/cli/backend/src/constants.d.ts +63 -2
- package/dist/cli/backend/src/constants.d.ts.map +1 -1
- package/dist/cli/backend/src/constants.js +67 -1
- package/dist/cli/backend/src/constants.js.map +1 -1
- package/dist/cli/backend/src/services/agent/agent-registration.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/agent/agent-registration.service.js +16 -2
- package/dist/cli/backend/src/services/agent/agent-registration.service.js.map +1 -1
- package/dist/cli/backend/src/services/agent/codex-daemon.utils.d.ts +16 -0
- package/dist/cli/backend/src/services/agent/codex-daemon.utils.d.ts.map +1 -1
- package/dist/cli/backend/src/services/agent/codex-daemon.utils.js +44 -0
- package/dist/cli/backend/src/services/agent/codex-daemon.utils.js.map +1 -1
- package/dist/cli/backend/src/services/agent/control-plane-guard.service.d.ts +14 -2
- package/dist/cli/backend/src/services/agent/control-plane-guard.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/agent/control-plane-guard.service.js +18 -3
- package/dist/cli/backend/src/services/agent/control-plane-guard.service.js.map +1 -1
- package/dist/cli/backend/src/services/agent/credential-guard.service.d.ts +153 -0
- package/dist/cli/backend/src/services/agent/credential-guard.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/agent/credential-guard.service.js +314 -0
- package/dist/cli/backend/src/services/agent/credential-guard.service.js.map +1 -0
- package/dist/cli/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.js +5 -0
- package/dist/cli/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.js.map +1 -1
- package/dist/cli/backend/src/services/agent/runtime-agent.service.abstract.d.ts +39 -0
- package/dist/cli/backend/src/services/agent/runtime-agent.service.abstract.d.ts.map +1 -1
- package/dist/cli/backend/src/services/agent/runtime-agent.service.abstract.js +132 -3
- package/dist/cli/backend/src/services/agent/runtime-agent.service.abstract.js.map +1 -1
- package/dist/cli/backend/src/services/core/credential-files.d.ts +26 -0
- package/dist/cli/backend/src/services/core/credential-files.d.ts.map +1 -0
- package/dist/cli/backend/src/services/core/credential-files.js +60 -0
- package/dist/cli/backend/src/services/core/credential-files.js.map +1 -0
- package/dist/cli/backend/src/services/messaging/input-blocked-retry.service.d.ts +10 -0
- package/dist/cli/backend/src/services/messaging/input-blocked-retry.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/messaging/input-blocked-retry.service.js +16 -0
- package/dist/cli/backend/src/services/messaging/input-blocked-retry.service.js.map +1 -1
- package/dist/cli/backend/src/services/messaging/sub-agent-message-queue.service.d.ts +10 -0
- package/dist/cli/backend/src/services/messaging/sub-agent-message-queue.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/messaging/sub-agent-message-queue.service.js +21 -0
- package/dist/cli/backend/src/services/messaging/sub-agent-message-queue.service.js.map +1 -1
- package/dist/cli/backend/src/services/session/input-ledger.d.ts +115 -0
- package/dist/cli/backend/src/services/session/input-ledger.d.ts.map +1 -0
- package/dist/cli/backend/src/services/session/input-ledger.js +144 -0
- package/dist/cli/backend/src/services/session/input-ledger.js.map +1 -0
- package/dist/cli/backend/src/services/session/session-command-helper.d.ts +26 -0
- package/dist/cli/backend/src/services/session/session-command-helper.d.ts.map +1 -1
- package/dist/cli/backend/src/services/session/session-command-helper.js +156 -37
- package/dist/cli/backend/src/services/session/session-command-helper.js.map +1 -1
- package/dist/cli/backend/src/services/session/tui-input-guard.d.ts +40 -0
- package/dist/cli/backend/src/services/session/tui-input-guard.d.ts.map +1 -1
- package/dist/cli/backend/src/services/session/tui-input-guard.js +95 -0
- package/dist/cli/backend/src/services/session/tui-input-guard.js.map +1 -1
- package/dist/cli/backend/src/websocket/terminal.gateway.d.ts.map +1 -1
- package/dist/cli/backend/src/websocket/terminal.gateway.js +4 -1
- package/dist/cli/backend/src/websocket/terminal.gateway.js.map +1 -1
- package/package.json +1 -1
- package/packages/crewly-agent/src/runtime/credential-guard.test.ts +48 -0
- package/packages/crewly-agent/src/runtime/credential-guard.ts +56 -0
- package/packages/crewly-agent/src/runtime/tool-registry.ts +15 -0
- package/packages/crewly-agent/src/runtime/web-search.tool.test.ts +18 -0
- package/packages/crewly-agent/src/runtime/web-search.tool.ts +36 -14
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Input ledger — who last put input into each agent's terminal.
|
|
3
|
+
*
|
|
4
|
+
* Text reaches an agent's PTY input box from exactly two places:
|
|
5
|
+
* - the harness's guarded paste (`SessionCommandHelper.sendMessage`), which
|
|
6
|
+
* records each paste here with {@link noteHarnessPaste};
|
|
7
|
+
* - outside input: the dashboard / phone terminal (TerminalGateway) and the
|
|
8
|
+
* raw-input HTTP surfaces (`/terminal/:s/write` raw mode, `/input`,
|
|
9
|
+
* `/key`, `/sessions/:name/write` raw mode), which record it here with
|
|
10
|
+
* {@link noteOutsideInput}. These are the only writers of raw input a
|
|
11
|
+
* person (the owner, or another agent typing raw) can reach.
|
|
12
|
+
*
|
|
13
|
+
* So when the box holds text and no outside input arrived since the
|
|
14
|
+
* harness's own pastes, that text is the harness's — it cannot be the
|
|
15
|
+
* owner's (1.20.207 Ella: two of our pastes, concatenated, sat unsent for
|
|
16
|
+
* nine hours because the box no longer matched one recorded paste).
|
|
17
|
+
*
|
|
18
|
+
* Other harness writes (OAuth relogin, runtime-terms answers, `/clear`) are
|
|
19
|
+
* submitted at once and leave nothing in the box; they are neither.
|
|
20
|
+
*
|
|
21
|
+
* Module-level and dependency-free so the gateway, controllers and the
|
|
22
|
+
* session helper can share it without import cycles. In memory: a restart
|
|
23
|
+
* recreates every session, which starts a new ledger.
|
|
24
|
+
*
|
|
25
|
+
* @module services/session/input-ledger
|
|
26
|
+
*/
|
|
27
|
+
/** One harness paste. */
|
|
28
|
+
export interface HarnessPaste {
|
|
29
|
+
/** The pasted message */
|
|
30
|
+
message: string;
|
|
31
|
+
/** Epoch ms of the paste */
|
|
32
|
+
at: number;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Record a harness paste into a session's input box.
|
|
36
|
+
*
|
|
37
|
+
* @param sessionName - The session
|
|
38
|
+
* @param message - What was pasted
|
|
39
|
+
* @param at - When (ms)
|
|
40
|
+
*/
|
|
41
|
+
export declare function noteHarnessPaste(sessionName: string, message: string, at?: number): void;
|
|
42
|
+
/**
|
|
43
|
+
* Record outside input into a session (owner keystrokes / pastes from the
|
|
44
|
+
* dashboard or phone terminal, raw writes through the input API). The
|
|
45
|
+
* harness's earlier pastes can no longer be told apart from it.
|
|
46
|
+
*
|
|
47
|
+
* @param sessionName - The session
|
|
48
|
+
* @param at - When (ms)
|
|
49
|
+
*/
|
|
50
|
+
export declare function noteOutsideInput(sessionName: string, at?: number): void;
|
|
51
|
+
/**
|
|
52
|
+
* The harness pastes made since the last outside input, oldest first.
|
|
53
|
+
*
|
|
54
|
+
* @param sessionName - The session
|
|
55
|
+
* @returns The pastes (a copy)
|
|
56
|
+
*/
|
|
57
|
+
export declare function harnessPastesSinceOutsideInput(sessionName: string): HarnessPaste[];
|
|
58
|
+
/**
|
|
59
|
+
* When outside input last arrived, if ever.
|
|
60
|
+
*
|
|
61
|
+
* @param sessionName - The session
|
|
62
|
+
* @returns Epoch ms, or undefined
|
|
63
|
+
*/
|
|
64
|
+
export declare function lastOutsideInputAt(sessionName: string): number | undefined;
|
|
65
|
+
/**
|
|
66
|
+
* Forget pastes the box no longer holds: those submitted (by our Enter),
|
|
67
|
+
* or older than `before` when the box reads empty.
|
|
68
|
+
*
|
|
69
|
+
* @param sessionName - The session
|
|
70
|
+
* @param keep - Pastes to keep
|
|
71
|
+
*/
|
|
72
|
+
export declare function keepHarnessPastes(sessionName: string, keep: (p: HarnessPaste) => boolean): void;
|
|
73
|
+
/**
|
|
74
|
+
* Record the exact marker the box showed for one of our pastes.
|
|
75
|
+
*
|
|
76
|
+
* @param sessionName - The session
|
|
77
|
+
* @param marker - The marker text ("[Pasted text #2 +5 lines]")
|
|
78
|
+
* @param message - The paste it stands for
|
|
79
|
+
*/
|
|
80
|
+
export declare function noteShownMarker(sessionName: string, marker: string, message: string): void;
|
|
81
|
+
/**
|
|
82
|
+
* Markers the box showed for our pastes.
|
|
83
|
+
*
|
|
84
|
+
* @param sessionName - The session
|
|
85
|
+
* @returns Marker → message pairs (a copy)
|
|
86
|
+
*/
|
|
87
|
+
export declare function shownMarkers(sessionName: string): Array<{
|
|
88
|
+
marker: string;
|
|
89
|
+
message: string;
|
|
90
|
+
}>;
|
|
91
|
+
/**
|
|
92
|
+
* Forget shown markers (the box no longer holds them).
|
|
93
|
+
*
|
|
94
|
+
* @param sessionName - The session
|
|
95
|
+
* @param keep - Markers to keep
|
|
96
|
+
*/
|
|
97
|
+
export declare function keepShownMarkers(sessionName: string, keep: (m: {
|
|
98
|
+
marker: string;
|
|
99
|
+
message: string;
|
|
100
|
+
}) => boolean): void;
|
|
101
|
+
/**
|
|
102
|
+
* Forget a session's ledger (session created, killed, runtime relaunched).
|
|
103
|
+
*
|
|
104
|
+
* @param sessionName - The session
|
|
105
|
+
*/
|
|
106
|
+
export declare function forgetInputLedger(sessionName: string): void;
|
|
107
|
+
/**
|
|
108
|
+
* Sessions with harness pastes on record.
|
|
109
|
+
*
|
|
110
|
+
* @returns Session names
|
|
111
|
+
*/
|
|
112
|
+
export declare function sessionsWithHarnessPastes(): string[];
|
|
113
|
+
/** Clear every ledger (tests). */
|
|
114
|
+
export declare function resetInputLedgerForTesting(): void;
|
|
115
|
+
//# sourceMappingURL=input-ledger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"input-ledger.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/session/input-ledger.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,yBAAyB;AACzB,MAAM,WAAW,YAAY;IAC5B,yBAAyB;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,4BAA4B;IAC5B,EAAE,EAAE,MAAM,CAAC;CACX;AAcD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,GAAE,MAAmB,GAAG,IAAI,CAKpG;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,GAAE,MAAmB,GAAG,IAAI,CAKnF;AAED;;;;;GAKG;AACH,wBAAgB,8BAA8B,CAAC,WAAW,EAAE,MAAM,GAAG,YAAY,EAAE,CAElF;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAE1E;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,YAAY,KAAK,OAAO,GAAG,IAAI,CAI/F;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAK1F;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,KAAK,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAE5F;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,KAAK,OAAO,GAAG,IAAI,CAGrH;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAE3D;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,IAAI,MAAM,EAAE,CAEpD;AAED,kCAAkC;AAClC,wBAAgB,0BAA0B,IAAI,IAAI,CAEjD"}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Input ledger — who last put input into each agent's terminal.
|
|
3
|
+
*
|
|
4
|
+
* Text reaches an agent's PTY input box from exactly two places:
|
|
5
|
+
* - the harness's guarded paste (`SessionCommandHelper.sendMessage`), which
|
|
6
|
+
* records each paste here with {@link noteHarnessPaste};
|
|
7
|
+
* - outside input: the dashboard / phone terminal (TerminalGateway) and the
|
|
8
|
+
* raw-input HTTP surfaces (`/terminal/:s/write` raw mode, `/input`,
|
|
9
|
+
* `/key`, `/sessions/:name/write` raw mode), which record it here with
|
|
10
|
+
* {@link noteOutsideInput}. These are the only writers of raw input a
|
|
11
|
+
* person (the owner, or another agent typing raw) can reach.
|
|
12
|
+
*
|
|
13
|
+
* So when the box holds text and no outside input arrived since the
|
|
14
|
+
* harness's own pastes, that text is the harness's — it cannot be the
|
|
15
|
+
* owner's (1.20.207 Ella: two of our pastes, concatenated, sat unsent for
|
|
16
|
+
* nine hours because the box no longer matched one recorded paste).
|
|
17
|
+
*
|
|
18
|
+
* Other harness writes (OAuth relogin, runtime-terms answers, `/clear`) are
|
|
19
|
+
* submitted at once and leave nothing in the box; they are neither.
|
|
20
|
+
*
|
|
21
|
+
* Module-level and dependency-free so the gateway, controllers and the
|
|
22
|
+
* session helper can share it without import cycles. In memory: a restart
|
|
23
|
+
* recreates every session, which starts a new ledger.
|
|
24
|
+
*
|
|
25
|
+
* @module services/session/input-ledger
|
|
26
|
+
*/
|
|
27
|
+
/** Most pastes remembered per session */
|
|
28
|
+
const MAX_PASTES = 10;
|
|
29
|
+
const ledgers = new Map();
|
|
30
|
+
/**
|
|
31
|
+
* Record a harness paste into a session's input box.
|
|
32
|
+
*
|
|
33
|
+
* @param sessionName - The session
|
|
34
|
+
* @param message - What was pasted
|
|
35
|
+
* @param at - When (ms)
|
|
36
|
+
*/
|
|
37
|
+
export function noteHarnessPaste(sessionName, message, at = Date.now()) {
|
|
38
|
+
const ledger = ledgers.get(sessionName) ?? { pastes: [], shown: [] };
|
|
39
|
+
ledger.pastes.push({ message, at });
|
|
40
|
+
if (ledger.pastes.length > MAX_PASTES)
|
|
41
|
+
ledger.pastes.splice(0, ledger.pastes.length - MAX_PASTES);
|
|
42
|
+
ledgers.set(sessionName, ledger);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Record outside input into a session (owner keystrokes / pastes from the
|
|
46
|
+
* dashboard or phone terminal, raw writes through the input API). The
|
|
47
|
+
* harness's earlier pastes can no longer be told apart from it.
|
|
48
|
+
*
|
|
49
|
+
* @param sessionName - The session
|
|
50
|
+
* @param at - When (ms)
|
|
51
|
+
*/
|
|
52
|
+
export function noteOutsideInput(sessionName, at = Date.now()) {
|
|
53
|
+
const ledger = ledgers.get(sessionName) ?? { pastes: [], shown: [] };
|
|
54
|
+
ledger.lastOutsideInputAt = at;
|
|
55
|
+
ledger.pastes = [];
|
|
56
|
+
ledgers.set(sessionName, ledger);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The harness pastes made since the last outside input, oldest first.
|
|
60
|
+
*
|
|
61
|
+
* @param sessionName - The session
|
|
62
|
+
* @returns The pastes (a copy)
|
|
63
|
+
*/
|
|
64
|
+
export function harnessPastesSinceOutsideInput(sessionName) {
|
|
65
|
+
return [...(ledgers.get(sessionName)?.pastes ?? [])];
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* When outside input last arrived, if ever.
|
|
69
|
+
*
|
|
70
|
+
* @param sessionName - The session
|
|
71
|
+
* @returns Epoch ms, or undefined
|
|
72
|
+
*/
|
|
73
|
+
export function lastOutsideInputAt(sessionName) {
|
|
74
|
+
return ledgers.get(sessionName)?.lastOutsideInputAt;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Forget pastes the box no longer holds: those submitted (by our Enter),
|
|
78
|
+
* or older than `before` when the box reads empty.
|
|
79
|
+
*
|
|
80
|
+
* @param sessionName - The session
|
|
81
|
+
* @param keep - Pastes to keep
|
|
82
|
+
*/
|
|
83
|
+
export function keepHarnessPastes(sessionName, keep) {
|
|
84
|
+
const ledger = ledgers.get(sessionName);
|
|
85
|
+
if (!ledger)
|
|
86
|
+
return;
|
|
87
|
+
ledger.pastes = ledger.pastes.filter(keep);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Record the exact marker the box showed for one of our pastes.
|
|
91
|
+
*
|
|
92
|
+
* @param sessionName - The session
|
|
93
|
+
* @param marker - The marker text ("[Pasted text #2 +5 lines]")
|
|
94
|
+
* @param message - The paste it stands for
|
|
95
|
+
*/
|
|
96
|
+
export function noteShownMarker(sessionName, marker, message) {
|
|
97
|
+
const ledger = ledgers.get(sessionName) ?? { pastes: [], shown: [] };
|
|
98
|
+
if (!ledger.shown.some((m) => m.marker === marker))
|
|
99
|
+
ledger.shown.push({ marker, message });
|
|
100
|
+
if (ledger.shown.length > MAX_PASTES)
|
|
101
|
+
ledger.shown.splice(0, ledger.shown.length - MAX_PASTES);
|
|
102
|
+
ledgers.set(sessionName, ledger);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Markers the box showed for our pastes.
|
|
106
|
+
*
|
|
107
|
+
* @param sessionName - The session
|
|
108
|
+
* @returns Marker → message pairs (a copy)
|
|
109
|
+
*/
|
|
110
|
+
export function shownMarkers(sessionName) {
|
|
111
|
+
return [...(ledgers.get(sessionName)?.shown ?? [])];
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Forget shown markers (the box no longer holds them).
|
|
115
|
+
*
|
|
116
|
+
* @param sessionName - The session
|
|
117
|
+
* @param keep - Markers to keep
|
|
118
|
+
*/
|
|
119
|
+
export function keepShownMarkers(sessionName, keep) {
|
|
120
|
+
const ledger = ledgers.get(sessionName);
|
|
121
|
+
if (ledger)
|
|
122
|
+
ledger.shown = ledger.shown.filter(keep);
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Forget a session's ledger (session created, killed, runtime relaunched).
|
|
126
|
+
*
|
|
127
|
+
* @param sessionName - The session
|
|
128
|
+
*/
|
|
129
|
+
export function forgetInputLedger(sessionName) {
|
|
130
|
+
ledgers.delete(sessionName);
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Sessions with harness pastes on record.
|
|
134
|
+
*
|
|
135
|
+
* @returns Session names
|
|
136
|
+
*/
|
|
137
|
+
export function sessionsWithHarnessPastes() {
|
|
138
|
+
return [...ledgers.entries()].filter(([, l]) => l.pastes.length > 0 || l.shown.length > 0).map(([s]) => s);
|
|
139
|
+
}
|
|
140
|
+
/** Clear every ledger (tests). */
|
|
141
|
+
export function resetInputLedgerForTesting() {
|
|
142
|
+
ledgers.clear();
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=input-ledger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"input-ledger.js","sourceRoot":"","sources":["../../../../../../backend/src/services/session/input-ledger.ts"],"names":[],"mappings":"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"}
|
|
@@ -68,6 +68,32 @@ export declare class SessionCommandHelper {
|
|
|
68
68
|
* @param sessionName - The session
|
|
69
69
|
*/
|
|
70
70
|
static forgetOwnPaste(sessionName: string): void;
|
|
71
|
+
/**
|
|
72
|
+
* Forget everything about a session's input: its paste record and its
|
|
73
|
+
* input ledger (session created or killed, runtime relaunched).
|
|
74
|
+
*
|
|
75
|
+
* @param sessionName - The session
|
|
76
|
+
*/
|
|
77
|
+
static resetSessionInput(sessionName: string): void;
|
|
78
|
+
/**
|
|
79
|
+
* Called with the messages our Enter just submitted from the box (a lost
|
|
80
|
+
* Enter, or a paste that rendered late). Wired to drop queued copies of
|
|
81
|
+
* them, so a held retry does not deliver them a second time.
|
|
82
|
+
*/
|
|
83
|
+
static onOwnPasteSubmitted: ((sessionName: string, messages: string[]) => void) | null;
|
|
84
|
+
/**
|
|
85
|
+
* Called once when an idle agent's box has held text that is not ours
|
|
86
|
+
* and not provably anyone else's for STUCK_INPUT_NOTIFY_MS. Wired to the
|
|
87
|
+
* blocked-input notice.
|
|
88
|
+
*/
|
|
89
|
+
static onStuckInput: ((sessionName: string, info: {
|
|
90
|
+
inputLength: number;
|
|
91
|
+
forMs: number;
|
|
92
|
+
}) => void) | null;
|
|
93
|
+
/** Per session: since when an idle agent's box held text we could not attribute, and whether it was reported */
|
|
94
|
+
private static readonly stuckSince;
|
|
95
|
+
/** The latest helper, for sessions the watcher only knows from the ledger */
|
|
96
|
+
private static lastHelper;
|
|
71
97
|
/**
|
|
72
98
|
* Whether a paste of ours is on record for a session (tests, diagnostics).
|
|
73
99
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-command-helper.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/session/session-command-helper.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAOhF,OAAO,
|
|
1
|
+
{"version":3,"file":"session-command-helper.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/session/session-command-helper.ts"],"names":[],"mappings":"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"}
|
|
@@ -21,8 +21,9 @@ import { delay } from '../../utils/async.utils.js';
|
|
|
21
21
|
import { assertNotSecretEnvKey } from '../../utils/secret-env.js';
|
|
22
22
|
import { quietShellLine } from '../../utils/shell-history.js';
|
|
23
23
|
import { PtyActivityTrackerService } from '../agent/pty-activity-tracker.service.js';
|
|
24
|
-
import { classifyTuiInput, pasteShowsAs, screenShowsTurnInProgress, TuiInputGuardError } from './tui-input-guard.js';
|
|
24
|
+
import { boxHoldsOnlyOwnPastes, classifyTuiInput, pasteShowsAs, screenShowsTurnInProgress, TuiInputGuardError, TuiPasteHoldError } from './tui-input-guard.js';
|
|
25
25
|
import { noteHarnessWrite } from '../trace/turn-origin.js';
|
|
26
|
+
import { forgetInputLedger, harnessPastesSinceOutsideInput, resetInputLedgerForTesting as resetInputLedgerForTestingHook, keepHarnessPastes, keepShownMarkers, noteHarnessPaste, noteShownMarker, sessionsWithHarnessPastes, shownMarkers, } from './input-ledger.js';
|
|
26
27
|
/**
|
|
27
28
|
* Key code mappings for special keys
|
|
28
29
|
*/
|
|
@@ -109,6 +110,34 @@ export class SessionCommandHelper {
|
|
|
109
110
|
SessionCommandHelper.ownPastes.delete(sessionName);
|
|
110
111
|
SessionCommandHelper.stopWatchingIfIdle();
|
|
111
112
|
}
|
|
113
|
+
/**
|
|
114
|
+
* Forget everything about a session's input: its paste record and its
|
|
115
|
+
* input ledger (session created or killed, runtime relaunched).
|
|
116
|
+
*
|
|
117
|
+
* @param sessionName - The session
|
|
118
|
+
*/
|
|
119
|
+
static resetSessionInput(sessionName) {
|
|
120
|
+
SessionCommandHelper.ownPastes.delete(sessionName);
|
|
121
|
+
SessionCommandHelper.stuckSince.delete(sessionName);
|
|
122
|
+
forgetInputLedger(sessionName);
|
|
123
|
+
SessionCommandHelper.stopWatchingIfIdle();
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Called with the messages our Enter just submitted from the box (a lost
|
|
127
|
+
* Enter, or a paste that rendered late). Wired to drop queued copies of
|
|
128
|
+
* them, so a held retry does not deliver them a second time.
|
|
129
|
+
*/
|
|
130
|
+
static onOwnPasteSubmitted = null;
|
|
131
|
+
/**
|
|
132
|
+
* Called once when an idle agent's box has held text that is not ours
|
|
133
|
+
* and not provably anyone else's for STUCK_INPUT_NOTIFY_MS. Wired to the
|
|
134
|
+
* blocked-input notice.
|
|
135
|
+
*/
|
|
136
|
+
static onStuckInput = null;
|
|
137
|
+
/** Per session: since when an idle agent's box held text we could not attribute, and whether it was reported */
|
|
138
|
+
static stuckSince = new Map();
|
|
139
|
+
/** The latest helper, for sessions the watcher only knows from the ledger */
|
|
140
|
+
static lastHelper = null;
|
|
112
141
|
/**
|
|
113
142
|
* Whether a paste of ours is on record for a session (tests, diagnostics).
|
|
114
143
|
*
|
|
@@ -125,7 +154,10 @@ export class SessionCommandHelper {
|
|
|
125
154
|
* @param message - What was pasted
|
|
126
155
|
*/
|
|
127
156
|
recordOwnPaste(sessionName, message) {
|
|
128
|
-
|
|
157
|
+
const at = SessionCommandHelper.now();
|
|
158
|
+
SessionCommandHelper.ownPastes.set(sessionName, { message, pastedAt: at, helper: this });
|
|
159
|
+
noteHarnessPaste(sessionName, message, at);
|
|
160
|
+
SessionCommandHelper.lastHelper = this;
|
|
129
161
|
SessionCommandHelper.startWatching();
|
|
130
162
|
}
|
|
131
163
|
/** Whether recording a paste starts the timed watcher (tests drive it by hand). */
|
|
@@ -141,7 +173,9 @@ export class SessionCommandHelper {
|
|
|
141
173
|
}
|
|
142
174
|
/** Stop the watcher once no paste is on record. */
|
|
143
175
|
static stopWatchingIfIdle() {
|
|
144
|
-
if (
|
|
176
|
+
if (!SessionCommandHelper.watchTimer)
|
|
177
|
+
return;
|
|
178
|
+
if (SessionCommandHelper.ownPastes.size > 0 || sessionsWithHarnessPastes().length > 0)
|
|
145
179
|
return;
|
|
146
180
|
clearInterval(SessionCommandHelper.watchTimer);
|
|
147
181
|
SessionCommandHelper.watchTimer = null;
|
|
@@ -155,18 +189,42 @@ export class SessionCommandHelper {
|
|
|
155
189
|
* @returns Resolves when every session was looked at
|
|
156
190
|
*/
|
|
157
191
|
static async watchOwnPastes() {
|
|
158
|
-
|
|
192
|
+
const sessions = new Set([...SessionCommandHelper.ownPastes.keys(), ...sessionsWithHarnessPastes()]);
|
|
193
|
+
for (const sessionName of sessions) {
|
|
159
194
|
if (SessionCommandHelper.inFlight.has(sessionName))
|
|
160
195
|
continue;
|
|
196
|
+
const helper = SessionCommandHelper.ownPastes.get(sessionName)?.helper ?? SessionCommandHelper.lastHelper;
|
|
197
|
+
if (!helper)
|
|
198
|
+
continue;
|
|
161
199
|
try {
|
|
162
|
-
if (!
|
|
163
|
-
SessionCommandHelper.
|
|
200
|
+
if (!helper.backend.sessionExists(sessionName)) {
|
|
201
|
+
SessionCommandHelper.resetSessionInput(sessionName);
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
const reading = helper.readInputBox(sessionName, '', 'recovery');
|
|
205
|
+
if (reading.state === 'empty' || reading.state === 'unknown') {
|
|
206
|
+
SessionCommandHelper.stuckSince.delete(sessionName);
|
|
164
207
|
continue;
|
|
165
208
|
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
209
|
+
if (!(await helper.isAgentIdle(sessionName)))
|
|
210
|
+
continue;
|
|
211
|
+
if (reading.ownPasteMarker) {
|
|
212
|
+
const outcome = await helper.ensureOwnPasteSubmitted(sessionName);
|
|
213
|
+
helper.logger.warn('Our earlier paste was still in the input box of an idle agent — pressed Enter on it once', { sessionName, outcome });
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
// An idle agent's box holds text we cannot attribute to our pastes,
|
|
217
|
+
// though no outside input arrived since them (else the ledger would
|
|
218
|
+
// be empty and we would not be here). Not submitted: text the harness
|
|
219
|
+
// cannot prove it wrote is never submitted (2026-10-03 incident).
|
|
220
|
+
// Reported once if it stays.
|
|
221
|
+
const now = SessionCommandHelper.now();
|
|
222
|
+
const stuck = SessionCommandHelper.stuckSince.get(sessionName) ?? { since: now, reported: false };
|
|
223
|
+
SessionCommandHelper.stuckSince.set(sessionName, stuck);
|
|
224
|
+
if (!stuck.reported && now - stuck.since >= TUI_INPUT_GUARD.STUCK_INPUT_NOTIFY_MS) {
|
|
225
|
+
stuck.reported = true;
|
|
226
|
+
helper.logger.warn('An idle agent\'s input box has held text Crewly cannot attribute for a long time', { sessionName, inputLength: reading.text.length });
|
|
227
|
+
SessionCommandHelper.onStuckInput?.(sessionName, { inputLength: reading.text.length, forMs: now - stuck.since });
|
|
170
228
|
}
|
|
171
229
|
}
|
|
172
230
|
catch {
|
|
@@ -233,6 +291,9 @@ export class SessionCommandHelper {
|
|
|
233
291
|
constructor(backend) {
|
|
234
292
|
this.logger = LoggerService.getInstance().createComponentLogger('SessionCommandHelper');
|
|
235
293
|
this.backend = backend;
|
|
294
|
+
// Any helper can read a box (they share the one backend): the watcher
|
|
295
|
+
// uses the latest for sessions it only knows from the input ledger.
|
|
296
|
+
SessionCommandHelper.lastHelper = this;
|
|
236
297
|
}
|
|
237
298
|
/**
|
|
238
299
|
* Get a session by name, throwing an error if it doesn't exist.
|
|
@@ -315,16 +376,43 @@ export class SessionCommandHelper {
|
|
|
315
376
|
});
|
|
316
377
|
// Step 1: the box must be readable and empty before we type.
|
|
317
378
|
let before = this.readInputBox(sessionName, message, 'before-write');
|
|
379
|
+
// Never paste while an earlier paste of ours may still land in the box
|
|
380
|
+
// (pasted, not yet seen — a busy Claude Code renders it seconds late):
|
|
381
|
+
// two pastes in one box are one prompt nobody can take apart
|
|
382
|
+
// (1.20.207 Ella). When it is this very message, wait for it a while
|
|
383
|
+
// and submit it — that is this delivery.
|
|
384
|
+
const pending = SessionCommandHelper.ownPastes.get(sessionName);
|
|
385
|
+
if (pending && pending.shown === undefined && !before.ownPasteMarker) {
|
|
386
|
+
if (pending.message === message) {
|
|
387
|
+
const deadline = pending.pastedAt + TUI_INPUT_GUARD.PASTE_PENDING_HOLD_MS;
|
|
388
|
+
const maxPolls = Math.ceil(TUI_INPUT_GUARD.PASTE_PENDING_HOLD_MS / TUI_INPUT_GUARD.PASTE_RENDER_POLL_MS);
|
|
389
|
+
for (let i = 0; i < maxPolls && SessionCommandHelper.now() < deadline && before.state === 'empty'; i++) {
|
|
390
|
+
await delay(TUI_INPUT_GUARD.PASTE_RENDER_POLL_MS);
|
|
391
|
+
before = this.readInputBox(sessionName, message, 'before-write');
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
if (!before.ownPasteMarker) {
|
|
395
|
+
this.logger.info('Not pasting: an earlier paste of ours may still render in the input box — holding this message', {
|
|
396
|
+
sessionName,
|
|
397
|
+
pendingForMs: SessionCommandHelper.now() - pending.pastedAt,
|
|
398
|
+
sameMessage: pending.message === message,
|
|
399
|
+
});
|
|
400
|
+
throw new TuiPasteHoldError('pending-paste');
|
|
401
|
+
}
|
|
402
|
+
}
|
|
318
403
|
if (before.ownPasteMarker) {
|
|
319
404
|
// An earlier paste of ours is still in the box (its Enter was lost,
|
|
320
|
-
// or it rendered late): submit it first. When it
|
|
405
|
+
// or it rendered late): submit it first. When it holds this very
|
|
321
406
|
// message, submitting it IS this delivery — never paste it twice.
|
|
322
|
-
const sameMessage =
|
|
323
|
-
this.logger.warn('An earlier paste of ours is still in the input box — submitting it', { sessionName, sameMessage });
|
|
407
|
+
const sameMessage = (before.ownPasteMessages ?? []).includes(message);
|
|
408
|
+
this.logger.warn('An earlier paste of ours is still in the input box — submitting it', { sessionName, sameMessage, pastes: before.ownPasteMessages?.length ?? 1 });
|
|
324
409
|
const outcome = await this.ensureOwnPasteSubmitted(sessionName);
|
|
325
410
|
if (sameMessage && outcome === 'submitted')
|
|
326
411
|
return;
|
|
327
|
-
|
|
412
|
+
// Do not paste on top of what we just submitted: hold this message
|
|
413
|
+
// until the box has settled (it may still be clearing, or our Enter
|
|
414
|
+
// may not have taken — 1.20.207 02:12).
|
|
415
|
+
throw new TuiPasteHoldError('just-submitted');
|
|
328
416
|
}
|
|
329
417
|
if (before.state === 'ours' && !before.ownPasteMarker) {
|
|
330
418
|
// Our own earlier paste of this very message: safe to clear.
|
|
@@ -410,14 +498,26 @@ export class SessionCommandHelper {
|
|
|
410
498
|
session.write('\r');
|
|
411
499
|
await delay(TUI_INPUT_GUARD.OWN_MARKER_SUBMIT_SETTLE_MS);
|
|
412
500
|
const after = this.readInputBox(sessionName, '', 'recovery');
|
|
413
|
-
// One Enter on it, whatever happened: never press Enter on
|
|
414
|
-
// again from
|
|
501
|
+
// One Enter on it, whatever happened: never press Enter on those
|
|
502
|
+
// pastes again — not from the record, not from the ledger.
|
|
503
|
+
const messages = reading.ownPasteMessages ?? [];
|
|
415
504
|
SessionCommandHelper.forgetOwnPaste(sessionName);
|
|
416
|
-
|
|
505
|
+
keepHarnessPastes(sessionName, (p) => !messages.includes(p.message));
|
|
506
|
+
keepShownMarkers(sessionName, (m) => !messages.includes(m.message));
|
|
507
|
+
if (after.state !== 'empty' && after.state !== 'unknown' && after.text.trim() === reading.text.trim()) {
|
|
417
508
|
this.logger.warn('Our pasted message is still in the input box after Enter — not delivered', { sessionName });
|
|
418
509
|
return 'stuck';
|
|
419
510
|
}
|
|
420
|
-
this.logger.info('Submitted our own paste whose Enter had been lost', { sessionName });
|
|
511
|
+
this.logger.info('Submitted our own paste whose Enter had been lost', { sessionName, pastes: messages.length });
|
|
512
|
+
SessionCommandHelper.stuckSince.delete(sessionName);
|
|
513
|
+
if (messages.length > 0) {
|
|
514
|
+
try {
|
|
515
|
+
SessionCommandHelper.onOwnPasteSubmitted?.(sessionName, messages);
|
|
516
|
+
}
|
|
517
|
+
catch {
|
|
518
|
+
// The hook only tidies queued copies; delivery already happened.
|
|
519
|
+
}
|
|
520
|
+
}
|
|
421
521
|
return 'submitted';
|
|
422
522
|
}
|
|
423
523
|
/**
|
|
@@ -426,6 +526,8 @@ export class SessionCommandHelper {
|
|
|
426
526
|
static resetOwnPasteMarkersForTesting() {
|
|
427
527
|
SessionCommandHelper.ownPastes.clear();
|
|
428
528
|
SessionCommandHelper.inFlight.clear();
|
|
529
|
+
SessionCommandHelper.stuckSince.clear();
|
|
530
|
+
resetInputLedgerForTestingHook();
|
|
429
531
|
SessionCommandHelper.stopWatchingIfIdle();
|
|
430
532
|
}
|
|
431
533
|
/**
|
|
@@ -442,7 +544,7 @@ export class SessionCommandHelper {
|
|
|
442
544
|
async sendShellLine(sessionName, line) {
|
|
443
545
|
const session = this.getSessionOrThrow(sessionName);
|
|
444
546
|
// A shell line means the runtime is (re)starting in this session.
|
|
445
|
-
SessionCommandHelper.
|
|
547
|
+
SessionCommandHelper.resetSessionInput(sessionName);
|
|
446
548
|
session.write(`\x1b[200~${line}\x1b[201~`);
|
|
447
549
|
await delay(Math.min(SESSION_COMMAND_DELAYS.MESSAGE_DELAY + Math.ceil(line.length / 10), 5000));
|
|
448
550
|
session.write('\r');
|
|
@@ -509,26 +611,43 @@ export class SessionCommandHelper {
|
|
|
509
611
|
* @returns The reading, marked `ownPasteMarker` when the box shows our paste
|
|
510
612
|
*/
|
|
511
613
|
applyOwnPaste(sessionName, reading) {
|
|
512
|
-
|
|
513
|
-
if (!rec || reading.state === 'unknown')
|
|
614
|
+
if (reading.state === 'unknown')
|
|
514
615
|
return reading;
|
|
515
616
|
const now = SessionCommandHelper.now();
|
|
516
|
-
if (
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
SessionCommandHelper.forgetOwnPaste(sessionName);
|
|
522
|
-
return reading;
|
|
617
|
+
if (reading.state === 'empty') {
|
|
618
|
+
// Nothing of ours in the box now. Pastes older than the render
|
|
619
|
+
// window were submitted (or lost); markers the box showed are gone.
|
|
620
|
+
keepHarnessPastes(sessionName, (p) => now - p.at < TUI_INPUT_GUARD.PASTE_PENDING_HOLD_MS);
|
|
621
|
+
keepShownMarkers(sessionName, () => false);
|
|
523
622
|
}
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
623
|
+
const rec = SessionCommandHelper.ownPastes.get(sessionName);
|
|
624
|
+
if (rec) {
|
|
625
|
+
if (rec.shown !== undefined) {
|
|
626
|
+
if (reading.ownPasteMarker) {
|
|
627
|
+
rec.lastSeenAt = now;
|
|
628
|
+
return { ...reading, ownPasteMessages: [rec.message] };
|
|
629
|
+
}
|
|
630
|
+
SessionCommandHelper.forgetOwnPaste(sessionName);
|
|
631
|
+
}
|
|
632
|
+
else if (reading.state !== 'empty') {
|
|
633
|
+
if (pasteShowsAs(reading.text, rec.message)) {
|
|
634
|
+
rec.shown = reading.text.trim();
|
|
635
|
+
rec.lastSeenAt = now;
|
|
636
|
+
if (/^\[Pasted /i.test(rec.shown))
|
|
637
|
+
noteShownMarker(sessionName, rec.shown, rec.message);
|
|
638
|
+
return { ...reading, state: 'ours', ownPasteMarker: true, ownPasteMessages: [rec.message] };
|
|
639
|
+
}
|
|
640
|
+
SessionCommandHelper.forgetOwnPaste(sessionName);
|
|
641
|
+
}
|
|
530
642
|
}
|
|
531
|
-
|
|
643
|
+
if (reading.state === 'empty' || reading.ownPasteMarker)
|
|
644
|
+
return reading;
|
|
645
|
+
// No single recorded paste matches. The box may still be made up only
|
|
646
|
+
// of our pastes — several run together, or one whose record ended —
|
|
647
|
+
// with no outside input since (input-ledger).
|
|
648
|
+
const messages = boxHoldsOnlyOwnPastes(reading.text, harnessPastesSinceOutsideInput(sessionName).map((p) => p.message), shownMarkers(sessionName));
|
|
649
|
+
if (messages)
|
|
650
|
+
return { ...reading, state: 'ours', ownPasteMarker: true, ownPasteMessages: messages };
|
|
532
651
|
return reading;
|
|
533
652
|
}
|
|
534
653
|
/**
|
|
@@ -740,7 +859,7 @@ export class SessionCommandHelper {
|
|
|
740
859
|
* Kill a session
|
|
741
860
|
*/
|
|
742
861
|
async killSession(sessionName) {
|
|
743
|
-
SessionCommandHelper.
|
|
862
|
+
SessionCommandHelper.resetSessionInput(sessionName);
|
|
744
863
|
await this.backend.killSession(sessionName);
|
|
745
864
|
this.logger.info('Session killed', { sessionName });
|
|
746
865
|
}
|
|
@@ -754,7 +873,7 @@ export class SessionCommandHelper {
|
|
|
754
873
|
*/
|
|
755
874
|
async createSession(sessionName, cwd, options) {
|
|
756
875
|
this.logger.info('Creating session', { sessionName, cwd });
|
|
757
|
-
SessionCommandHelper.
|
|
876
|
+
SessionCommandHelper.resetSessionInput(sessionName);
|
|
758
877
|
// Default to shell if no command specified
|
|
759
878
|
const command = options?.command || process.env.SHELL || '/bin/bash';
|
|
760
879
|
const session = await this.backend.createSession(sessionName, {
|