@oh-my-pi/pi-coding-agent 17.2.5 → 17.2.6
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/CHANGELOG.md +18 -0
- package/dist/{CHANGELOG-q2w43aw3.md → CHANGELOG-gs76k6wc.md} +18 -0
- package/dist/cli.js +3378 -3333
- package/dist/types/cli/gc-cli.d.ts +1 -0
- package/dist/types/launch/client.d.ts +9 -1
- package/dist/types/launch/protocol.d.ts +17 -0
- package/dist/types/modes/components/btw-panel.d.ts +3 -0
- package/dist/types/modes/controllers/btw-controller.d.ts +2 -0
- package/dist/types/modes/controllers/command-controller.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +4 -1
- package/dist/types/modes/types.d.ts +3 -1
- package/dist/types/security/contracts/schemas.d.ts +405 -403
- package/dist/types/session/agent-session-types.d.ts +5 -0
- package/dist/types/session/agent-session.d.ts +26 -2
- package/dist/types/session/launch-completion.d.ts +10 -0
- package/dist/types/session/session-entries.d.ts +15 -1
- package/dist/types/session/session-manager.d.ts +7 -0
- package/dist/types/session/yield-queue.d.ts +6 -1
- package/dist/types/tools/index.d.ts +9 -0
- package/package.json +12 -12
- package/src/cli/gc-cli.ts +641 -21
- package/src/config/model-discovery.ts +14 -14
- package/src/config/model-registry.ts +10 -8
- package/src/config/settings.ts +1 -1
- package/src/export/html/index.ts +10 -3
- package/src/export/share.ts +4 -0
- package/src/launch/broker.ts +222 -6
- package/src/launch/client.ts +161 -9
- package/src/launch/protocol.ts +52 -0
- package/src/main.ts +13 -6
- package/src/mcp/config-writer.ts +1 -1
- package/src/modes/components/btw-panel.ts +41 -4
- package/src/modes/controllers/btw-controller.ts +55 -7
- package/src/modes/controllers/command-controller.ts +31 -0
- package/src/modes/controllers/input-controller.ts +24 -7
- package/src/modes/interactive-mode.ts +16 -2
- package/src/modes/types.ts +8 -1
- package/src/prompts/session/launch-completion.md +1 -0
- package/src/sdk.ts +15 -0
- package/src/security/contracts/schemas.ts +205 -183
- package/src/security/contracts/validation.ts +5 -1
- package/src/security/store.ts +2 -2
- package/src/session/agent-session-types.ts +6 -0
- package/src/session/agent-session.ts +191 -14
- package/src/session/launch-completion.ts +37 -0
- package/src/session/session-context.ts +26 -0
- package/src/session/session-entries.ts +17 -1
- package/src/session/session-manager.ts +21 -0
- package/src/session/yield-queue.ts +121 -16
- package/src/slash-commands/builtin-registry.ts +10 -0
- package/src/tools/browser/cmux/cmux-tab.ts +92 -4
- package/src/tools/hub/launch.ts +122 -6
- package/src/tools/index.ts +9 -0
- package/dist/types/config/file-lock.d.ts +0 -29
- package/src/config/file-lock.ts +0 -164
|
@@ -250,7 +250,7 @@ export class InputController {
|
|
|
250
250
|
this.#btwBranchListenerInstalled = true;
|
|
251
251
|
this.ctx.ui.addInputListener(data => {
|
|
252
252
|
if (!matchesKey(data, "b")) return undefined;
|
|
253
|
-
if (!this.ctx.
|
|
253
|
+
if (!this.ctx.handlesBtwBranchKey()) return undefined;
|
|
254
254
|
if (this.ctx.ui.getFocused() !== this.ctx.editor) return undefined;
|
|
255
255
|
if (this.ctx.editor.getText().trim()) return undefined;
|
|
256
256
|
void this.ctx.handleBtwBranchKey();
|
|
@@ -801,6 +801,19 @@ export class InputController {
|
|
|
801
801
|
this.ctx.queueCompactionMessage(text, "steer", images);
|
|
802
802
|
return;
|
|
803
803
|
}
|
|
804
|
+
// Extension commands are local actions. Execute them before the normal
|
|
805
|
+
// submission path creates an optimistic user message; otherwise a
|
|
806
|
+
// consumed command remains rendered like a prompt sent to the model.
|
|
807
|
+
if (this.#isLocalExtensionCommand(text)) {
|
|
808
|
+
this.ctx.editor.clearDraft(text);
|
|
809
|
+
try {
|
|
810
|
+
await this.ctx.session.prompt(text, { images: inputImages });
|
|
811
|
+
} catch (error) {
|
|
812
|
+
this.ctx.editor.setText(text);
|
|
813
|
+
this.ctx.showError(error instanceof Error ? error.message : String(error));
|
|
814
|
+
}
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
804
817
|
|
|
805
818
|
// If streaming, use prompt() with steer behavior
|
|
806
819
|
// This handles extension commands (execute immediately), prompt template expansion, and queueing
|
|
@@ -914,14 +927,18 @@ export class InputController {
|
|
|
914
927
|
* Local extension commands are consumed before reaching the shared session
|
|
915
928
|
* title gate and must not name the conversation.
|
|
916
929
|
*/
|
|
917
|
-
#
|
|
918
|
-
const runner = this.ctx.session.extensionRunner;
|
|
930
|
+
#isLocalExtensionCommand(text: string): boolean {
|
|
919
931
|
const extensionCommandSpace = text.indexOf(" ");
|
|
920
|
-
|
|
932
|
+
return (
|
|
921
933
|
text.startsWith("/") &&
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
934
|
+
this.ctx.session.extensionRunner?.getCommand(
|
|
935
|
+
extensionCommandSpace === -1 ? text.slice(1) : text.slice(1, extensionCommandSpace),
|
|
936
|
+
) !== undefined
|
|
937
|
+
);
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
#maybeStartTitleGeneration(text: string): void {
|
|
941
|
+
if (this.#isLocalExtensionCommand(text)) {
|
|
925
942
|
return;
|
|
926
943
|
}
|
|
927
944
|
this.ctx.session.maybeStartTitleGeneration(text, () => {
|
|
@@ -4513,6 +4513,10 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
4513
4513
|
return this.#commandController.handleFreshCommand();
|
|
4514
4514
|
}
|
|
4515
4515
|
|
|
4516
|
+
handleResetContextCommand(): Promise<void> {
|
|
4517
|
+
return this.#commandController.handleResetContextCommand();
|
|
4518
|
+
}
|
|
4519
|
+
|
|
4516
4520
|
async handleDropCommand(): Promise<void> {
|
|
4517
4521
|
if (this.#vibeSessionTransitionBlocked()) return;
|
|
4518
4522
|
this.#prepareSessionSwitch();
|
|
@@ -4844,6 +4848,11 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
4844
4848
|
return this.#btwController.canBranch();
|
|
4845
4849
|
}
|
|
4846
4850
|
|
|
4851
|
+
/** Reserves plain `b` only after /btw has a completed branch action to handle. */
|
|
4852
|
+
handlesBtwBranchKey(): boolean {
|
|
4853
|
+
return this.#btwController.handlesBranchKey();
|
|
4854
|
+
}
|
|
4855
|
+
|
|
4847
4856
|
handleBtwBranchKey(): Promise<boolean> {
|
|
4848
4857
|
return this.#btwController.handleBranch();
|
|
4849
4858
|
}
|
|
@@ -4856,9 +4865,14 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
4856
4865
|
return this.#btwController.handleCopy();
|
|
4857
4866
|
}
|
|
4858
4867
|
|
|
4859
|
-
async handleBtwBranch(
|
|
4868
|
+
async handleBtwBranch(
|
|
4869
|
+
question: string,
|
|
4870
|
+
assistantMessage: AssistantMessage,
|
|
4871
|
+
leafId: string,
|
|
4872
|
+
sessionId: string,
|
|
4873
|
+
): Promise<void> {
|
|
4860
4874
|
try {
|
|
4861
|
-
const result = await this.session.branchFromBtw(question, assistantMessage);
|
|
4875
|
+
const result = await this.session.branchFromBtw(question, assistantMessage, leafId, sessionId);
|
|
4862
4876
|
if (result.cancelled) {
|
|
4863
4877
|
this.showStatus("/btw branch cancelled", { dim: true });
|
|
4864
4878
|
return;
|
package/src/modes/types.ts
CHANGED
|
@@ -350,6 +350,7 @@ export interface InteractiveModeContext {
|
|
|
350
350
|
handleDebugTranscriptCommand(): Promise<void>;
|
|
351
351
|
handleClearCommand(): Promise<void>;
|
|
352
352
|
handleFreshCommand(): Promise<void>;
|
|
353
|
+
handleResetContextCommand(): Promise<void>;
|
|
353
354
|
handleDropCommand(): Promise<void>;
|
|
354
355
|
handleForkCommand(): Promise<void>;
|
|
355
356
|
handleBashCommand(command: string, excludeFromContext?: boolean): Promise<void>;
|
|
@@ -418,9 +419,15 @@ export interface InteractiveModeContext {
|
|
|
418
419
|
handleBtwEscape(): boolean;
|
|
419
420
|
handleBtwBranchKey(): Promise<boolean>;
|
|
420
421
|
canBranchBtw(): boolean;
|
|
422
|
+
handlesBtwBranchKey(): boolean;
|
|
421
423
|
canCopyBtw(): boolean;
|
|
422
424
|
handleBtwCopyKey(): Promise<boolean>;
|
|
423
|
-
handleBtwBranch(
|
|
425
|
+
handleBtwBranch(
|
|
426
|
+
question: string,
|
|
427
|
+
assistantMessage: AssistantMessage,
|
|
428
|
+
leafId: string,
|
|
429
|
+
sessionId: string,
|
|
430
|
+
): Promise<void>;
|
|
424
431
|
handleOmfgCommand(complaint: string): Promise<void>;
|
|
425
432
|
hasActiveOmfg(): boolean;
|
|
426
433
|
handleOmfgEscape(): boolean;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Supervised process {{name}} {{state}} {{#if hasExitCode}}with exit code {{exitCode}}{{else}}without an exit code{{/if}}.
|
package/src/sdk.ts
CHANGED
|
@@ -1686,6 +1686,7 @@ async function createAgentSessionScoped(options: CreateAgentSessionOptions): Pro
|
|
|
1686
1686
|
// entries capture it at fetch time and are dropped at injection if a newer
|
|
1687
1687
|
// mutation (any tool) bumped it in the meantime.
|
|
1688
1688
|
const fileMutationVersions = new Map<string, number>();
|
|
1689
|
+
const disposeCallbacks = new Set<() => void>();
|
|
1689
1690
|
const activeToolNames = new Set<string>();
|
|
1690
1691
|
const toolRegistry = new Map<string, Tool>();
|
|
1691
1692
|
const setActiveToolNames = (names: Iterable<string>): void => {
|
|
@@ -1739,6 +1740,7 @@ async function createAgentSessionScoped(options: CreateAgentSessionOptions): Pro
|
|
|
1739
1740
|
trackEvalExecution: (execution, abortController) =>
|
|
1740
1741
|
session ? session.trackEvalExecution(execution, abortController) : execution,
|
|
1741
1742
|
getSessionId: () => sessionManager.getSessionId?.() ?? null,
|
|
1743
|
+
isDisposed: () => session?.isDisposed ?? false,
|
|
1742
1744
|
getHindsightSessionState: () => session?.getHindsightSessionState(),
|
|
1743
1745
|
getMnemopiSessionState: () => session?.getMnemopiSessionState(),
|
|
1744
1746
|
getAgentId: () => resolvedAgentId,
|
|
@@ -1765,6 +1767,14 @@ async function createAgentSessionScoped(options: CreateAgentSessionOptions): Pro
|
|
|
1765
1767
|
recordEvalSubagentUsage: output => sessionManager.recordEvalSubagentOutput(output),
|
|
1766
1768
|
getClientBridge: () => session?.clientBridge,
|
|
1767
1769
|
queueDeferredDiagnostics: entry => session?.yieldQueue.enqueue(LSP_LATE_DIAGNOSTIC_MESSAGE_TYPE, entry),
|
|
1770
|
+
queueLaunchCompletion: notification =>
|
|
1771
|
+
session?.queueLaunchCompletion(notification) ??
|
|
1772
|
+
Promise.reject(new Error("Session unavailable for launch completion delivery")),
|
|
1773
|
+
registerDisposeCallback: callback => {
|
|
1774
|
+
disposeCallbacks.add(callback);
|
|
1775
|
+
return () => disposeCallbacks.delete(callback);
|
|
1776
|
+
},
|
|
1777
|
+
registerSessionChangeCallback: callback => session?.registerSessionChangeCallback(callback),
|
|
1768
1778
|
bumpFileMutationVersion: path => {
|
|
1769
1779
|
const next = (fileMutationVersions.get(path) ?? 0) + 1;
|
|
1770
1780
|
fileMutationVersions.set(path, next);
|
|
@@ -3307,6 +3317,9 @@ async function createAgentSessionScoped(options: CreateAgentSessionOptions): Pro
|
|
|
3307
3317
|
const id = sessionManager.getSessionId?.();
|
|
3308
3318
|
return id ? `${id}-advisor` : null;
|
|
3309
3319
|
},
|
|
3320
|
+
queueLaunchCompletion: notification =>
|
|
3321
|
+
session?.queueLaunchCompletion(notification) ??
|
|
3322
|
+
Promise.reject(new Error("Session unavailable for launch completion delivery")),
|
|
3310
3323
|
getAgentId: () => "advisor",
|
|
3311
3324
|
// The primary's availability signals are wrong for advisors: their tool
|
|
3312
3325
|
// slate is filtered separately at runtime (default read/grep/glob, no
|
|
@@ -3522,6 +3535,8 @@ async function createAgentSessionScoped(options: CreateAgentSessionOptions): Pro
|
|
|
3522
3535
|
unsubscribeCredentialDisabled?.();
|
|
3523
3536
|
unsubscribeMcpNotifications?.();
|
|
3524
3537
|
unregisterMcpPostmortem?.();
|
|
3538
|
+
for (const callback of disposeCallbacks) callback();
|
|
3539
|
+
disposeCallbacks.clear();
|
|
3525
3540
|
// Drop refs so the process-global postmortem list doesn't retain
|
|
3526
3541
|
// the bridge closure past explicit dispose.
|
|
3527
3542
|
unsubscribeMcpNotifications = undefined;
|
|
@@ -1,201 +1,223 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { once } from "@oh-my-pi/pi-utils";
|
|
2
|
+
import { scope } from "arktype";
|
|
2
3
|
|
|
3
|
-
const
|
|
4
|
-
|
|
4
|
+
export const getSecurityContractSchemas = once(() => {
|
|
5
|
+
// Security schemas validate only during security scans, so lazy construction
|
|
6
|
+
// with interpreted traversal avoids eager JIT startup tax without changing correctness.
|
|
7
|
+
const { type } = scope({}, { jitless: true });
|
|
5
8
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
name: "string > 0",
|
|
9
|
-
"version?": "string",
|
|
10
|
-
"vendor?": "string",
|
|
11
|
-
"revision?": "string",
|
|
12
|
-
"pluginVersion?": "string",
|
|
13
|
-
});
|
|
9
|
+
const stringRecordSchema = type({ "[string]": "string" });
|
|
10
|
+
const unknownRecordSchema = type({ "[string]": "unknown" });
|
|
14
11
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
"vendorFingerprints?": stringRecordSchema,
|
|
21
|
-
"upstream?": {
|
|
22
|
-
"repository?": "string",
|
|
12
|
+
const securityProducerSchema = type({
|
|
13
|
+
kind: "'omp-native' | 'codex-security-bundle' | 'codex-security-cloud' | 'sarif-import'",
|
|
14
|
+
name: "string > 0",
|
|
15
|
+
"version?": "string",
|
|
16
|
+
"vendor?": "string",
|
|
23
17
|
"revision?": "string",
|
|
24
|
-
"packageVersion?": "string",
|
|
25
18
|
"pluginVersion?": "string",
|
|
26
|
-
|
|
27
|
-
},
|
|
28
|
-
"metadata?": unknownRecordSchema,
|
|
29
|
-
});
|
|
19
|
+
});
|
|
30
20
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
21
|
+
const securityProvenanceSchema = type({
|
|
22
|
+
producer: securityProducerSchema,
|
|
23
|
+
createdAt: "string > 0",
|
|
24
|
+
"importedAt?": "string",
|
|
25
|
+
"sourceIds?": stringRecordSchema,
|
|
26
|
+
"vendorFingerprints?": stringRecordSchema,
|
|
27
|
+
"upstream?": {
|
|
28
|
+
"repository?": "string",
|
|
29
|
+
"revision?": "string",
|
|
30
|
+
"packageVersion?": "string",
|
|
31
|
+
"pluginVersion?": "string",
|
|
32
|
+
"archiveSha256?": "string",
|
|
33
|
+
},
|
|
34
|
+
"metadata?": unknownRecordSchema,
|
|
35
|
+
});
|
|
39
36
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
});
|
|
37
|
+
const securityLocationSchema = type({
|
|
38
|
+
path: "string > 0",
|
|
39
|
+
startLine: "number.integer >= 1",
|
|
40
|
+
"endLine?": "number.integer >= 1",
|
|
41
|
+
"startColumn?": "number.integer >= 1",
|
|
42
|
+
"endColumn?": "number.integer >= 1",
|
|
43
|
+
"role?": "string",
|
|
44
|
+
});
|
|
48
45
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
46
|
+
const securityEvidenceSchema = type({
|
|
47
|
+
id: "string > 0",
|
|
48
|
+
kind: "'code' | 'trace' | 'validation' | 'note'",
|
|
49
|
+
label: "string > 0",
|
|
50
|
+
explanation: "string",
|
|
51
|
+
"location?": securityLocationSchema,
|
|
52
|
+
"excerpt?": "string",
|
|
53
|
+
});
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
fingerprint: "string > 0",
|
|
59
|
-
ruleId: "string > 0",
|
|
60
|
-
"anchor?": "string",
|
|
61
|
-
title: "string > 0",
|
|
62
|
-
summary: "string",
|
|
63
|
-
severity: {
|
|
64
|
-
level: "'critical' | 'high' | 'medium' | 'low' | 'informational'",
|
|
65
|
-
"score?": "number",
|
|
66
|
-
"scoringSystem?": "string",
|
|
67
|
-
"vector?": "string",
|
|
68
|
-
"rationale?": "string",
|
|
69
|
-
},
|
|
70
|
-
confidence: {
|
|
71
|
-
level: "'high' | 'medium' | 'low'",
|
|
72
|
-
"rationale?": "string",
|
|
73
|
-
},
|
|
74
|
-
taxonomy: {
|
|
75
|
-
category: "string > 0",
|
|
76
|
-
cwe: "string[]",
|
|
77
|
-
"tags?": "string[]",
|
|
78
|
-
},
|
|
79
|
-
occurrences: securityOccurrenceSchema.array().atLeastLength(1),
|
|
80
|
-
evidence: securityEvidenceSchema.array(),
|
|
81
|
-
"remediation?": "string",
|
|
82
|
-
validation: {
|
|
83
|
-
status: "'unvalidated' | 'validated' | 'rejected' | 'partial' | 'error'",
|
|
84
|
-
"summary?": "string",
|
|
55
|
+
const securityOccurrenceSchema = type({
|
|
56
|
+
id: "string > 0",
|
|
57
|
+
locations: securityLocationSchema.array().atLeastLength(1),
|
|
85
58
|
evidenceIds: "string[]",
|
|
86
|
-
|
|
87
|
-
},
|
|
88
|
-
disposition: {
|
|
89
|
-
status: "'open' | 'false_positive' | 'accepted_risk' | 'fixed' | 'wont_fix'",
|
|
90
|
-
"rationale?": "string",
|
|
91
|
-
"updatedAt?": "string",
|
|
92
|
-
"actor?": "string",
|
|
93
|
-
},
|
|
94
|
-
provenance: securityProvenanceSchema,
|
|
95
|
-
"extensions?": unknownRecordSchema,
|
|
96
|
-
});
|
|
59
|
+
});
|
|
97
60
|
|
|
98
|
-
|
|
99
|
-
mode: "'repository' | 'scoped_path' | 'diff' | 'working_tree' | 'deep_repository' | 'imported'",
|
|
100
|
-
completeness: "'complete' | 'partial' | 'unknown'",
|
|
101
|
-
inventoryStrategy: "'repository' | 'scoped_path' | 'diff' | 'directory' | 'custom' | 'imported'",
|
|
102
|
-
includePaths: "string[]",
|
|
103
|
-
excludePaths: "string[]",
|
|
104
|
-
surfaces: type({
|
|
105
|
-
id: "string > 0",
|
|
106
|
-
label: "string > 0",
|
|
107
|
-
disposition: "'reported' | 'no_issue_found' | 'rejected' | 'not_applicable' | 'needs_follow_up'",
|
|
108
|
-
receiptRefs: "string[]",
|
|
109
|
-
"riskArea?": "string",
|
|
110
|
-
"notes?": "string",
|
|
111
|
-
}).array(),
|
|
112
|
-
explicitExclusions: type({ pattern: "string", reason: "string" }).array(),
|
|
113
|
-
deferred: type({
|
|
61
|
+
const securityFindingSchema = type({
|
|
114
62
|
id: "string > 0",
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
63
|
+
scanId: "string > 0",
|
|
64
|
+
fingerprint: "string > 0",
|
|
65
|
+
ruleId: "string > 0",
|
|
66
|
+
"anchor?": "string",
|
|
67
|
+
title: "string > 0",
|
|
68
|
+
summary: "string",
|
|
69
|
+
severity: {
|
|
70
|
+
level: "'critical' | 'high' | 'medium' | 'low' | 'informational'",
|
|
71
|
+
"score?": "number",
|
|
72
|
+
"scoringSystem?": "string",
|
|
73
|
+
"vector?": "string",
|
|
74
|
+
"rationale?": "string",
|
|
75
|
+
},
|
|
76
|
+
confidence: {
|
|
77
|
+
level: "'high' | 'medium' | 'low'",
|
|
78
|
+
"rationale?": "string",
|
|
79
|
+
},
|
|
80
|
+
taxonomy: {
|
|
81
|
+
category: "string > 0",
|
|
82
|
+
cwe: "string[]",
|
|
83
|
+
"tags?": "string[]",
|
|
84
|
+
},
|
|
85
|
+
occurrences: securityOccurrenceSchema.array().atLeastLength(1),
|
|
86
|
+
evidence: securityEvidenceSchema.array(),
|
|
87
|
+
"remediation?": "string",
|
|
88
|
+
validation: {
|
|
89
|
+
status: "'unvalidated' | 'validated' | 'rejected' | 'partial' | 'error'",
|
|
90
|
+
"summary?": "string",
|
|
91
|
+
evidenceIds: "string[]",
|
|
92
|
+
"validatedAt?": "string",
|
|
93
|
+
},
|
|
94
|
+
disposition: {
|
|
95
|
+
status: "'open' | 'false_positive' | 'accepted_risk' | 'fixed' | 'wont_fix'",
|
|
96
|
+
"rationale?": "string",
|
|
97
|
+
"updatedAt?": "string",
|
|
98
|
+
"actor?": "string",
|
|
99
|
+
},
|
|
100
|
+
provenance: securityProvenanceSchema,
|
|
101
|
+
"extensions?": unknownRecordSchema,
|
|
102
|
+
});
|
|
121
103
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
104
|
+
const securityCoverageSchema = type({
|
|
105
|
+
mode: "'repository' | 'scoped_path' | 'diff' | 'working_tree' | 'deep_repository' | 'imported'",
|
|
106
|
+
completeness: "'complete' | 'partial' | 'unknown'",
|
|
107
|
+
inventoryStrategy: "'repository' | 'scoped_path' | 'diff' | 'directory' | 'custom' | 'imported'",
|
|
108
|
+
includePaths: "string[]",
|
|
109
|
+
excludePaths: "string[]",
|
|
110
|
+
surfaces: type({
|
|
111
|
+
id: "string > 0",
|
|
112
|
+
label: "string > 0",
|
|
113
|
+
disposition: "'reported' | 'no_issue_found' | 'rejected' | 'not_applicable' | 'needs_follow_up'",
|
|
114
|
+
receiptRefs: "string[]",
|
|
115
|
+
"riskArea?": "string",
|
|
116
|
+
"notes?": "string",
|
|
117
|
+
}).array(),
|
|
118
|
+
explicitExclusions: type({ pattern: "string", reason: "string" }).array(),
|
|
119
|
+
deferred: type({
|
|
120
|
+
id: "string > 0",
|
|
121
|
+
reason: "string > 0",
|
|
122
|
+
"paths?": "string[]",
|
|
123
|
+
"surfaceIds?": "string[]",
|
|
124
|
+
}).array(),
|
|
125
|
+
"openQuestions?": type({ question: "string > 0", "followUpPrompt?": "string" }).array(),
|
|
126
|
+
});
|
|
133
127
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
existingState: "'absent' | 'empty' | 'archivable'",
|
|
146
|
-
},
|
|
147
|
-
model: { provider: "string > 0", modelId: "string > 0", "thinkingLevel?": "string" },
|
|
148
|
-
account: {
|
|
149
|
-
provider: "string > 0",
|
|
150
|
-
credentialId: "number.integer >= 1",
|
|
151
|
-
"accountId?": "string",
|
|
152
|
-
"email?": "string",
|
|
153
|
-
"organizationId?": "string",
|
|
154
|
-
"organizationName?": "string",
|
|
155
|
-
},
|
|
156
|
-
configFingerprint: "string > 0",
|
|
157
|
-
workflowFingerprint: "string > 0",
|
|
158
|
-
fingerprint: "string > 0",
|
|
159
|
-
});
|
|
128
|
+
const securityTargetSchema = type({
|
|
129
|
+
kind: "'repository' | 'scoped_path' | 'ref_diff' | 'working_tree' | 'imported'",
|
|
130
|
+
repositoryRoot: "string > 0",
|
|
131
|
+
displayName: "string > 0",
|
|
132
|
+
"revision?": "string",
|
|
133
|
+
"baseRevision?": "string",
|
|
134
|
+
"headRevision?": "string",
|
|
135
|
+
includePaths: "string[]",
|
|
136
|
+
excludePaths: "string[]",
|
|
137
|
+
treeDigest: "string > 0",
|
|
138
|
+
});
|
|
160
139
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
}
|
|
140
|
+
const securityScanPlanSchema = type({
|
|
141
|
+
documentType: "'omp-security.scan-plan'",
|
|
142
|
+
schemaVersion: "'1.0'",
|
|
143
|
+
id: "string > 0",
|
|
144
|
+
createdAt: "string > 0",
|
|
145
|
+
repositoryRoot: "string > 0",
|
|
146
|
+
target: securityTargetSchema,
|
|
147
|
+
knowledgeBases: type({ path: "string > 0", sha256: "string > 0", size: "number.integer >= 0" }).array(),
|
|
148
|
+
output: {
|
|
149
|
+
root: "string > 0",
|
|
150
|
+
archiveExisting: "boolean",
|
|
151
|
+
existingState: "'absent' | 'empty' | 'archivable'",
|
|
152
|
+
},
|
|
153
|
+
model: { provider: "string > 0", modelId: "string > 0", "thinkingLevel?": "string" },
|
|
154
|
+
account: {
|
|
155
|
+
provider: "string > 0",
|
|
156
|
+
credentialId: "number.integer >= 1",
|
|
157
|
+
"accountId?": "string",
|
|
158
|
+
"email?": "string",
|
|
159
|
+
"organizationId?": "string",
|
|
160
|
+
"organizationName?": "string",
|
|
161
|
+
},
|
|
162
|
+
configFingerprint: "string > 0",
|
|
163
|
+
workflowFingerprint: "string > 0",
|
|
164
|
+
fingerprint: "string > 0",
|
|
165
|
+
});
|
|
174
166
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
167
|
+
const securityScanMetricsSchema = type({
|
|
168
|
+
"runtimeMs?": "number >= 0",
|
|
169
|
+
"tokenUsage?": {
|
|
170
|
+
input: "number >= 0",
|
|
171
|
+
output: "number >= 0",
|
|
172
|
+
reasoning: "number >= 0",
|
|
173
|
+
cacheRead: "number >= 0",
|
|
174
|
+
cacheWrite: "number >= 0",
|
|
175
|
+
total: "number >= 0",
|
|
176
|
+
},
|
|
177
|
+
"cost?": "number >= 0",
|
|
178
|
+
"premiumRequests?": "number >= 0",
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
const securityScanSchema = type({
|
|
182
|
+
documentType: "'omp-security.scan'",
|
|
183
|
+
schemaVersion: "'1.0'",
|
|
184
|
+
id: "string > 0",
|
|
185
|
+
projectKey: "string > 0",
|
|
186
|
+
status: "'planned' | 'running' | 'completed' | 'partial' | 'cancelled' | 'failed'",
|
|
187
|
+
createdAt: "string > 0",
|
|
188
|
+
"startedAt?": "string",
|
|
189
|
+
"completedAt?": "string",
|
|
190
|
+
"plan?": securityScanPlanSchema,
|
|
191
|
+
target: securityTargetSchema,
|
|
192
|
+
producer: securityProducerSchema,
|
|
193
|
+
provenance: securityProvenanceSchema,
|
|
194
|
+
findingIds: "string[]",
|
|
195
|
+
coverage: securityCoverageSchema,
|
|
196
|
+
"reportRef?": "string",
|
|
197
|
+
"sarifRef?": "string",
|
|
198
|
+
"error?": "string",
|
|
199
|
+
"metrics?": securityScanMetricsSchema,
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
const securityScanBundleSchema = type({
|
|
203
|
+
scan: securityScanSchema,
|
|
204
|
+
findings: securityFindingSchema.array(),
|
|
205
|
+
"report?": "string",
|
|
206
|
+
"sarif?": unknownRecordSchema,
|
|
207
|
+
});
|
|
195
208
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
209
|
+
return {
|
|
210
|
+
securityProducerSchema,
|
|
211
|
+
securityProvenanceSchema,
|
|
212
|
+
securityLocationSchema,
|
|
213
|
+
securityEvidenceSchema,
|
|
214
|
+
securityOccurrenceSchema,
|
|
215
|
+
securityFindingSchema,
|
|
216
|
+
securityCoverageSchema,
|
|
217
|
+
securityTargetSchema,
|
|
218
|
+
securityScanPlanSchema,
|
|
219
|
+
securityScanMetricsSchema,
|
|
220
|
+
securityScanSchema,
|
|
221
|
+
securityScanBundleSchema,
|
|
222
|
+
};
|
|
201
223
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type } from "arktype";
|
|
2
|
-
import {
|
|
2
|
+
import { getSecurityContractSchemas } from "./schemas";
|
|
3
3
|
import type { SecurityFinding, SecurityScan, SecurityScanBundle, SecurityScanPlan } from "./types";
|
|
4
4
|
|
|
5
5
|
function schemaError(label: string, errors: type.errors): Error {
|
|
@@ -7,24 +7,28 @@ function schemaError(label: string, errors: type.errors): Error {
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
export function parseSecurityFinding(value: unknown): SecurityFinding {
|
|
10
|
+
const { securityFindingSchema } = getSecurityContractSchemas();
|
|
10
11
|
const result = securityFindingSchema(value);
|
|
11
12
|
if (result instanceof type.errors) throw schemaError("Security finding", result);
|
|
12
13
|
return result as SecurityFinding;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
export function parseSecurityScan(value: unknown): SecurityScan {
|
|
17
|
+
const { securityScanSchema } = getSecurityContractSchemas();
|
|
16
18
|
const result = securityScanSchema(value);
|
|
17
19
|
if (result instanceof type.errors) throw schemaError("Security scan", result);
|
|
18
20
|
return result as SecurityScan;
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
export function parseSecurityScanPlan(value: unknown): SecurityScanPlan {
|
|
24
|
+
const { securityScanPlanSchema } = getSecurityContractSchemas();
|
|
22
25
|
const result = securityScanPlanSchema(value);
|
|
23
26
|
if (result instanceof type.errors) throw schemaError("Security scan plan", result);
|
|
24
27
|
return result as SecurityScanPlan;
|
|
25
28
|
}
|
|
26
29
|
|
|
27
30
|
export function parseSecurityScanBundle(value: unknown): SecurityScanBundle {
|
|
31
|
+
const { securityScanBundleSchema } = getSecurityContractSchemas();
|
|
28
32
|
const result = securityScanBundleSchema(value);
|
|
29
33
|
if (result instanceof type.errors) throw schemaError("Security scan bundle", result);
|
|
30
34
|
const bundle = result as SecurityScanBundle;
|
package/src/security/store.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as fs from "node:fs/promises";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { getSecurityProjectDir, isEnoent } from "@oh-my-pi/pi-utils";
|
|
4
|
-
import { withFileLock } from "
|
|
4
|
+
import { withFileLock } from "@oh-my-pi/pi-utils/file-lock";
|
|
5
5
|
import * as git from "../utils/git";
|
|
6
6
|
import { compareSecurityLineage } from "./comparison";
|
|
7
7
|
import type {
|
|
@@ -34,7 +34,7 @@ const SECURITY_STORE_WRITE_CHAINS = new Map<string, Promise<unknown>>();
|
|
|
34
34
|
async function withSecurityStoreWrite<T>(key: string, operation: () => Promise<T>): Promise<T> {
|
|
35
35
|
const lockTarget = path.join(key, "index.json");
|
|
36
36
|
const run = (SECURITY_STORE_WRITE_CHAINS.get(key) ?? Promise.resolve()).then(() =>
|
|
37
|
-
withFileLock(lockTarget, operation, {
|
|
37
|
+
withFileLock(lockTarget, operation, { retries: 200, retryDelayMs: 50 }),
|
|
38
38
|
);
|
|
39
39
|
const guarded = run.catch(() => undefined);
|
|
40
40
|
SECURITY_STORE_WRITE_CHAINS.set(key, guarded);
|
|
@@ -389,5 +389,11 @@ export interface FreshSessionResult {
|
|
|
389
389
|
closedProviderSessions: number;
|
|
390
390
|
}
|
|
391
391
|
|
|
392
|
+
/** Outcome of an in-place `/reset` conversation-context reset. */
|
|
393
|
+
export interface ResetSessionContextResult {
|
|
394
|
+
/** Number of live messages dropped from the model's context. */
|
|
395
|
+
droppedCount: number;
|
|
396
|
+
}
|
|
397
|
+
|
|
392
398
|
/** Queued user content restored to the editor. */
|
|
393
399
|
export type RestoredQueuedMessage = { text: string; images?: ImageContent[] };
|