@grackle-ai/plugin-core 0.96.3
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/dist/codespace-handlers.d.ts +6 -0
- package/dist/codespace-handlers.d.ts.map +1 -0
- package/dist/codespace-handlers.js +66 -0
- package/dist/codespace-handlers.js.map +1 -0
- package/dist/cron-phase.d.ts +46 -0
- package/dist/cron-phase.d.ts.map +1 -0
- package/dist/cron-phase.js +88 -0
- package/dist/cron-phase.js.map +1 -0
- package/dist/dispatch-phase.d.ts +46 -0
- package/dist/dispatch-phase.d.ts.map +1 -0
- package/dist/dispatch-phase.js +99 -0
- package/dist/dispatch-phase.js.map +1 -0
- package/dist/environment-handlers.d.ts +16 -0
- package/dist/environment-handlers.d.ts.map +1 -0
- package/dist/environment-handlers.js +255 -0
- package/dist/environment-handlers.js.map +1 -0
- package/dist/environment-reconciliation.d.ts +35 -0
- package/dist/environment-reconciliation.d.ts.map +1 -0
- package/dist/environment-reconciliation.js +74 -0
- package/dist/environment-reconciliation.js.map +1 -0
- package/dist/escalation-handlers.d.ts +8 -0
- package/dist/escalation-handlers.d.ts.map +1 -0
- package/dist/escalation-handlers.js +60 -0
- package/dist/escalation-handlers.js.map +1 -0
- package/dist/finding-handlers.d.ts +8 -0
- package/dist/finding-handlers.d.ts.map +1 -0
- package/dist/finding-handlers.js +38 -0
- package/dist/finding-handlers.js.map +1 -0
- package/dist/grpc-proto-converters.d.ts +31 -0
- package/dist/grpc-proto-converters.d.ts.map +1 -0
- package/dist/grpc-proto-converters.js +222 -0
- package/dist/grpc-proto-converters.js.map +1 -0
- package/dist/grpc-service.d.ts +21 -0
- package/dist/grpc-service.d.ts.map +1 -0
- package/dist/grpc-service.js +50 -0
- package/dist/grpc-service.js.map +1 -0
- package/dist/grpc-shared.d.ts +22 -0
- package/dist/grpc-shared.d.ts.map +1 -0
- package/dist/grpc-shared.js +68 -0
- package/dist/grpc-shared.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/knowledge-handlers.d.ts +12 -0
- package/dist/knowledge-handlers.d.ts.map +1 -0
- package/dist/knowledge-handlers.js +149 -0
- package/dist/knowledge-handlers.js.map +1 -0
- package/dist/lifecycle-cleanup.d.ts +13 -0
- package/dist/lifecycle-cleanup.d.ts.map +1 -0
- package/dist/lifecycle-cleanup.js +35 -0
- package/dist/lifecycle-cleanup.js.map +1 -0
- package/dist/lifecycle.d.ts +28 -0
- package/dist/lifecycle.d.ts.map +1 -0
- package/dist/lifecycle.js +132 -0
- package/dist/lifecycle.js.map +1 -0
- package/dist/orphan-phase.d.ts +27 -0
- package/dist/orphan-phase.d.ts.map +1 -0
- package/dist/orphan-phase.js +69 -0
- package/dist/orphan-phase.js.map +1 -0
- package/dist/persona-handlers.d.ts +12 -0
- package/dist/persona-handlers.d.ts.map +1 -0
- package/dist/persona-handlers.js +156 -0
- package/dist/persona-handlers.js.map +1 -0
- package/dist/root-task-boot.d.ts +67 -0
- package/dist/root-task-boot.d.ts.map +1 -0
- package/dist/root-task-boot.js +234 -0
- package/dist/root-task-boot.js.map +1 -0
- package/dist/schedule-expression.d.ts +43 -0
- package/dist/schedule-expression.d.ts.map +1 -0
- package/dist/schedule-expression.js +105 -0
- package/dist/schedule-expression.js.map +1 -0
- package/dist/schedule-handlers.d.ts +12 -0
- package/dist/schedule-handlers.d.ts.map +1 -0
- package/dist/schedule-handlers.js +122 -0
- package/dist/schedule-handlers.js.map +1 -0
- package/dist/session-handlers.d.ts +40 -0
- package/dist/session-handlers.d.ts.map +1 -0
- package/dist/session-handlers.js +610 -0
- package/dist/session-handlers.js.map +1 -0
- package/dist/settings-handlers.d.ts +10 -0
- package/dist/settings-handlers.d.ts.map +1 -0
- package/dist/settings-handlers.js +80 -0
- package/dist/settings-handlers.js.map +1 -0
- package/dist/signals/escalation-auto.d.ts +20 -0
- package/dist/signals/escalation-auto.d.ts.map +1 -0
- package/dist/signals/escalation-auto.js +126 -0
- package/dist/signals/escalation-auto.js.map +1 -0
- package/dist/signals/orphan-reparent.d.ts +31 -0
- package/dist/signals/orphan-reparent.d.ts.map +1 -0
- package/dist/signals/orphan-reparent.js +175 -0
- package/dist/signals/orphan-reparent.js.map +1 -0
- package/dist/signals/sigchld.d.ts +12 -0
- package/dist/signals/sigchld.d.ts.map +1 -0
- package/dist/signals/sigchld.js +177 -0
- package/dist/signals/sigchld.js.map +1 -0
- package/dist/task-handlers.d.ts +22 -0
- package/dist/task-handlers.d.ts.map +1 -0
- package/dist/task-handlers.js +517 -0
- package/dist/task-handlers.js.map +1 -0
- package/dist/test-utils/integration-setup.d.ts +11 -0
- package/dist/test-utils/integration-setup.d.ts.map +1 -0
- package/dist/test-utils/integration-setup.js +26 -0
- package/dist/test-utils/integration-setup.js.map +1 -0
- package/dist/test-utils/mock-database.d.ts +152 -0
- package/dist/test-utils/mock-database.d.ts.map +1 -0
- package/dist/test-utils/mock-database.js +169 -0
- package/dist/test-utils/mock-database.js.map +1 -0
- package/dist/token-handlers.d.ts +12 -0
- package/dist/token-handlers.d.ts.map +1 -0
- package/dist/token-handlers.js +85 -0
- package/dist/token-handlers.js.map +1 -0
- package/dist/tsdoc-metadata.json +11 -0
- package/dist/utils/format-gh-error.d.ts +6 -0
- package/dist/utils/format-gh-error.d.ts.map +1 -0
- package/dist/utils/format-gh-error.js +30 -0
- package/dist/utils/format-gh-error.js.map +1 -0
- package/dist/workspace-handlers.d.ts +16 -0
- package/dist/workspace-handlers.d.ts.map +1 -0
- package/dist/workspace-handlers.js +146 -0
- package/dist/workspace-handlers.js.map +1 -0
- package/package.json +60 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { grackle } from "@grackle-ai/common";
|
|
2
|
+
/** Get the value of a setting by key. */
|
|
3
|
+
export declare function getSetting(req: grackle.GetSettingRequest): Promise<grackle.SettingResponse>;
|
|
4
|
+
/** Set the value of a setting. */
|
|
5
|
+
export declare function setSetting(req: grackle.SetSettingRequest): Promise<grackle.SettingResponse>;
|
|
6
|
+
/** Generate a new pairing code for web UI access. */
|
|
7
|
+
export declare function generatePairingCode(): Promise<grackle.PairingCodeResponse>;
|
|
8
|
+
/** Get the current version status (update available, current/latest versions). */
|
|
9
|
+
export declare function getVersionStatus(): Promise<grackle.VersionStatus>;
|
|
10
|
+
//# sourceMappingURL=settings-handlers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settings-handlers.d.ts","sourceRoot":"","sources":["../src/settings-handlers.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAQ7C,yCAAyC;AACzC,wBAAsB,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC,iBAAiB,GAAG,OAAO,CAAC,OAAO,CAAC,eAAe,CAAC,CASjG;AAED,kCAAkC;AAClC,wBAAsB,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC,iBAAiB,GAAG,OAAO,CAAC,OAAO,CAAC,eAAe,CAAC,CAqCjG;AAED,qDAAqD;AACrD,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAiBhF;AAED,kFAAkF;AAClF,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC,CAQvE"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { ConnectError, Code } from "@connectrpc/connect";
|
|
2
|
+
import { create } from "@bufbuild/protobuf";
|
|
3
|
+
import { grackle } from "@grackle-ai/common";
|
|
4
|
+
import { DEFAULT_WEB_PORT } from "@grackle-ai/common";
|
|
5
|
+
import { settingsStore, personaStore, envRegistry, isAllowedSettingKey } from "@grackle-ai/database";
|
|
6
|
+
import { generatePairingCode as authGeneratePairingCode } from "@grackle-ai/auth";
|
|
7
|
+
import { checkVersionStatus } from "@grackle-ai/core";
|
|
8
|
+
import { detectLanIp } from "@grackle-ai/core";
|
|
9
|
+
import { emit } from "@grackle-ai/core";
|
|
10
|
+
/** Get the value of a setting by key. */
|
|
11
|
+
export async function getSetting(req) {
|
|
12
|
+
if (!isAllowedSettingKey(req.key)) {
|
|
13
|
+
throw new ConnectError(`Setting key not allowed: ${req.key}`, Code.InvalidArgument);
|
|
14
|
+
}
|
|
15
|
+
const value = settingsStore.getSetting(req.key);
|
|
16
|
+
return create(grackle.SettingResponseSchema, {
|
|
17
|
+
key: req.key,
|
|
18
|
+
value: value ?? "",
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
/** Set the value of a setting. */
|
|
22
|
+
export async function setSetting(req) {
|
|
23
|
+
if (!isAllowedSettingKey(req.key)) {
|
|
24
|
+
throw new ConnectError(`Setting key not allowed: ${req.key}`, Code.InvalidArgument);
|
|
25
|
+
}
|
|
26
|
+
// Validate persona exists and has required fields when setting default_persona_id
|
|
27
|
+
if (req.key === "default_persona_id" && req.value) {
|
|
28
|
+
const persona = personaStore.getPersona(req.value);
|
|
29
|
+
if (!persona) {
|
|
30
|
+
throw new ConnectError(`Persona not found: ${req.value}`, Code.NotFound);
|
|
31
|
+
}
|
|
32
|
+
if (!persona.runtime || !persona.model) {
|
|
33
|
+
throw new ConnectError(`Persona "${persona.name}" must have runtime and model configured`, Code.FailedPrecondition);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
settingsStore.setSetting(req.key, req.value);
|
|
37
|
+
emit("setting.changed", { key: req.key, value: req.value });
|
|
38
|
+
// Sync the local environment's defaultRuntime when the default persona changes,
|
|
39
|
+
// so bootstrap pre-installs the correct runtime packages (fixes #1031).
|
|
40
|
+
if (req.key === "default_persona_id" && req.value) {
|
|
41
|
+
const newDefault = personaStore.getPersona(req.value);
|
|
42
|
+
if (newDefault?.runtime) {
|
|
43
|
+
const localEnv = envRegistry.getEnvironment("local");
|
|
44
|
+
if (localEnv && localEnv.defaultRuntime !== newDefault.runtime) {
|
|
45
|
+
envRegistry.updateDefaultRuntime("local", newDefault.runtime);
|
|
46
|
+
emit("environment.changed", {});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return create(grackle.SettingResponseSchema, {
|
|
51
|
+
key: req.key,
|
|
52
|
+
value: req.value,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
/** Generate a new pairing code for web UI access. */
|
|
56
|
+
export async function generatePairingCode() {
|
|
57
|
+
const code = authGeneratePairingCode();
|
|
58
|
+
if (!code) {
|
|
59
|
+
throw new ConnectError("Maximum active pairing codes reached. Wait for existing codes to expire.", Code.ResourceExhausted);
|
|
60
|
+
}
|
|
61
|
+
const webPort = parseInt(process.env.GRACKLE_WEB_PORT || String(DEFAULT_WEB_PORT), 10);
|
|
62
|
+
const bindHost = process.env.GRACKLE_HOST || "127.0.0.1";
|
|
63
|
+
const WILDCARD_ADDRESSES = new Set(["0.0.0.0", "::", "0:0:0:0:0:0:0:0"]);
|
|
64
|
+
const pairingHost = WILDCARD_ADDRESSES.has(bindHost)
|
|
65
|
+
? (detectLanIp() || "localhost")
|
|
66
|
+
: (bindHost === "127.0.0.1" || bindHost === "::1" ? "localhost" : bindHost);
|
|
67
|
+
const url = `http://${pairingHost}:${webPort}/pair?code=${code}`;
|
|
68
|
+
return create(grackle.PairingCodeResponseSchema, { code, url });
|
|
69
|
+
}
|
|
70
|
+
/** Get the current version status (update available, current/latest versions). */
|
|
71
|
+
export async function getVersionStatus() {
|
|
72
|
+
const status = await checkVersionStatus();
|
|
73
|
+
return create(grackle.VersionStatusSchema, {
|
|
74
|
+
currentVersion: status.currentVersion,
|
|
75
|
+
latestVersion: status.latestVersion,
|
|
76
|
+
updateAvailable: status.updateAvailable,
|
|
77
|
+
isDocker: status.isDocker,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=settings-handlers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settings-handlers.js","sourceRoot":"","sources":["../src/settings-handlers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AACrG,OAAO,EAAE,mBAAmB,IAAI,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAClF,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAExC,yCAAyC;AACzC,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,GAA8B;IAC7D,IAAI,CAAC,mBAAmB,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;QAClC,MAAM,IAAI,YAAY,CAAC,4BAA4B,GAAG,CAAC,GAAG,EAAE,EAAE,IAAI,CAAC,eAAe,CAAC,CAAC;IACtF,CAAC;IACD,MAAM,KAAK,GAAG,aAAa,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAChD,OAAO,MAAM,CAAC,OAAO,CAAC,qBAAqB,EAAE;QAC3C,GAAG,EAAE,GAAG,CAAC,GAAG;QACZ,KAAK,EAAE,KAAK,IAAI,EAAE;KACnB,CAAC,CAAC;AACL,CAAC;AAED,kCAAkC;AAClC,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,GAA8B;IAC7D,IAAI,CAAC,mBAAmB,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;QAClC,MAAM,IAAI,YAAY,CAAC,4BAA4B,GAAG,CAAC,GAAG,EAAE,EAAE,IAAI,CAAC,eAAe,CAAC,CAAC;IACtF,CAAC;IACD,kFAAkF;IAClF,IAAI,GAAG,CAAC,GAAG,KAAK,oBAAoB,IAAI,GAAG,CAAC,KAAK,EAAE,CAAC;QAClD,MAAM,OAAO,GAAG,YAAY,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACnD,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,YAAY,CAAC,sBAAsB,GAAG,CAAC,KAAK,EAAE,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC3E,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;YACvC,MAAM,IAAI,YAAY,CACpB,YAAY,OAAO,CAAC,IAAI,0CAA0C,EAClE,IAAI,CAAC,kBAAkB,CACxB,CAAC;QACJ,CAAC;IACH,CAAC;IACD,aAAa,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC;IAC7C,IAAI,CAAC,iBAAiB,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC;IAE5D,gFAAgF;IAChF,wEAAwE;IACxE,IAAI,GAAG,CAAC,GAAG,KAAK,oBAAoB,IAAI,GAAG,CAAC,KAAK,EAAE,CAAC;QAClD,MAAM,UAAU,GAAG,YAAY,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACtD,IAAI,UAAU,EAAE,OAAO,EAAE,CAAC;YACxB,MAAM,QAAQ,GAAG,WAAW,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC;YACrD,IAAI,QAAQ,IAAI,QAAQ,CAAC,cAAc,KAAK,UAAU,CAAC,OAAO,EAAE,CAAC;gBAC/D,WAAW,CAAC,oBAAoB,CAAC,OAAO,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC;gBAC9D,IAAI,CAAC,qBAAqB,EAAE,EAAE,CAAC,CAAC;YAClC,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC,OAAO,CAAC,qBAAqB,EAAE;QAC3C,GAAG,EAAE,GAAG,CAAC,GAAG;QACZ,KAAK,EAAE,GAAG,CAAC,KAAK;KACjB,CAAC,CAAC;AACL,CAAC;AAED,qDAAqD;AACrD,MAAM,CAAC,KAAK,UAAU,mBAAmB;IACvC,MAAM,IAAI,GAAG,uBAAuB,EAAE,CAAC;IACvC,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,IAAI,YAAY,CACpB,0EAA0E,EAC1E,IAAI,CAAC,iBAAiB,CACvB,CAAC;IACJ,CAAC;IAED,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,MAAM,CAAC,gBAAgB,CAAC,EAAE,EAAE,CAAC,CAAC;IACvF,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,WAAW,CAAC;IACzD,MAAM,kBAAkB,GAAwB,IAAI,GAAG,CAAC,CAAC,SAAS,EAAE,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC;IAC9F,MAAM,WAAW,GAAG,kBAAkB,CAAC,GAAG,CAAC,QAAQ,CAAC;QAClD,CAAC,CAAC,CAAC,WAAW,EAAE,IAAI,WAAW,CAAC;QAChC,CAAC,CAAC,CAAC,QAAQ,KAAK,WAAW,IAAI,QAAQ,KAAK,KAAK,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;IAC9E,MAAM,GAAG,GAAG,UAAU,WAAW,IAAI,OAAO,cAAc,IAAI,EAAE,CAAC;IACjE,OAAO,MAAM,CAAC,OAAO,CAAC,yBAAyB,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;AAClE,CAAC;AAED,kFAAkF;AAClF,MAAM,CAAC,KAAK,UAAU,gBAAgB;IACpC,MAAM,MAAM,GAAG,MAAM,kBAAkB,EAAE,CAAC;IAC1C,OAAO,MAAM,CAAC,OAAO,CAAC,mBAAmB,EAAE;QACzC,cAAc,EAAE,MAAM,CAAC,cAAc;QACrC,aAAa,EAAE,MAAM,CAAC,aAAa;QACnC,eAAe,EAAE,MAAM,CAAC,eAAe;QACvC,QAAQ,EAAE,MAAM,CAAC,QAAQ;KAC1B,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-detection subscriber for human escalation.
|
|
3
|
+
*
|
|
4
|
+
* Watches for standalone (parentless, non-system-root) tasks that go IDLE
|
|
5
|
+
* and automatically creates an escalation on the agent's behalf.
|
|
6
|
+
* This is the safety net for simple topologies where there is no orchestrator
|
|
7
|
+
* to explicitly call `escalate_to_human`.
|
|
8
|
+
*/
|
|
9
|
+
import type { Disposable, PluginContext } from "@grackle-ai/core";
|
|
10
|
+
/**
|
|
11
|
+
* Create the auto-escalation event-bus subscriber.
|
|
12
|
+
*
|
|
13
|
+
* Watches for standalone (parentless, non-ROOT) tasks whose latest session
|
|
14
|
+
* goes IDLE and automatically creates an escalation.
|
|
15
|
+
*
|
|
16
|
+
* @param ctx - Plugin context providing event-bus access.
|
|
17
|
+
* @returns A Disposable that unsubscribes and clears dedup state.
|
|
18
|
+
*/
|
|
19
|
+
export declare function createEscalationAutoSubscriber(ctx: PluginContext): Disposable;
|
|
20
|
+
//# sourceMappingURL=escalation-auto.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"escalation-auto.d.ts","sourceRoot":"","sources":["../../src/signals/escalation-auto.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AASH,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAKlE;;;;;;;;GAQG;AACH,wBAAgB,8BAA8B,CAAC,GAAG,EAAE,aAAa,GAAG,UAAU,CAgC7E"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-detection subscriber for human escalation.
|
|
3
|
+
*
|
|
4
|
+
* Watches for standalone (parentless, non-system-root) tasks that go IDLE
|
|
5
|
+
* and automatically creates an escalation on the agent's behalf.
|
|
6
|
+
* This is the safety net for simple topologies where there is no orchestrator
|
|
7
|
+
* to explicitly call `escalate_to_human`.
|
|
8
|
+
*/
|
|
9
|
+
import { SESSION_STATUS, ROOT_TASK_ID } from "@grackle-ai/common";
|
|
10
|
+
import { taskStore, sessionStore, escalationStore } from "@grackle-ai/database";
|
|
11
|
+
import { readLastTextEntry } from "@grackle-ai/core";
|
|
12
|
+
import { routeEscalation } from "@grackle-ai/core";
|
|
13
|
+
import { logger } from "@grackle-ai/core";
|
|
14
|
+
import { ulid } from "ulid";
|
|
15
|
+
/** How long (ms) to remember a delivered notification before allowing re-delivery. */
|
|
16
|
+
const DEDUP_TTL_MS = 3_600_000; // 1 hour
|
|
17
|
+
/**
|
|
18
|
+
* Create the auto-escalation event-bus subscriber.
|
|
19
|
+
*
|
|
20
|
+
* Watches for standalone (parentless, non-ROOT) tasks whose latest session
|
|
21
|
+
* goes IDLE and automatically creates an escalation.
|
|
22
|
+
*
|
|
23
|
+
* @param ctx - Plugin context providing event-bus access.
|
|
24
|
+
* @returns A Disposable that unsubscribes and clears dedup state.
|
|
25
|
+
*/
|
|
26
|
+
export function createEscalationAutoSubscriber(ctx) {
|
|
27
|
+
/** Track delivered notifications to prevent duplicates: key -> delivery timestamp. */
|
|
28
|
+
const delivered = new Map();
|
|
29
|
+
const unsubscribe = ctx.subscribe((event) => {
|
|
30
|
+
if (event.type !== "task.updated") {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const taskId = event.payload.taskId;
|
|
34
|
+
if (!taskId) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
// Fire-and-forget async handler — errors are logged, never thrown
|
|
38
|
+
(async () => {
|
|
39
|
+
try {
|
|
40
|
+
await handleTaskUpdated(delivered, taskId);
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
logger.error({ err, taskId }, "Escalation auto-detect handler error");
|
|
44
|
+
}
|
|
45
|
+
})().catch(() => { });
|
|
46
|
+
});
|
|
47
|
+
logger.info("Escalation auto-detect subscriber initialized");
|
|
48
|
+
return {
|
|
49
|
+
dispose() {
|
|
50
|
+
unsubscribe();
|
|
51
|
+
delivered.clear();
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Handle a task.updated event: check if the task is a standalone root task
|
|
57
|
+
* whose latest session has gone IDLE, and if so, auto-escalate.
|
|
58
|
+
*/
|
|
59
|
+
async function handleTaskUpdated(delivered, taskId) {
|
|
60
|
+
const task = taskStore.getTask(taskId);
|
|
61
|
+
if (!task) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
// Only parentless tasks trigger auto-escalation (child tasks use SIGCHLD)
|
|
65
|
+
if (task.parentTaskId) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
// Exclude the system root task — it is always idle when nobody is chatting
|
|
69
|
+
if (taskId === ROOT_TASK_ID) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
// Check if the latest session is IDLE
|
|
73
|
+
const latestSession = sessionStore.getLatestSessionForTask(taskId);
|
|
74
|
+
if (!latestSession) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (latestSession.status !== SESSION_STATUS.IDLE) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
// Idempotency: don't re-deliver for the same task+session pair
|
|
81
|
+
const dedupeKey = `${taskId}:${latestSession.id}`;
|
|
82
|
+
const now = Date.now();
|
|
83
|
+
const previousDelivery = delivered.get(dedupeKey);
|
|
84
|
+
if (previousDelivery !== undefined && now - previousDelivery < DEDUP_TTL_MS) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
// Prune expired entries to prevent unbounded growth
|
|
88
|
+
pruneDelivered(delivered, now);
|
|
89
|
+
// Extract the last text message from the session log
|
|
90
|
+
const logPath = latestSession.logPath || undefined;
|
|
91
|
+
const lastEntry = logPath ? readLastTextEntry(logPath) : undefined;
|
|
92
|
+
const message = lastEntry?.content ?? "";
|
|
93
|
+
// Build task URL
|
|
94
|
+
const taskUrl = `/tasks/${taskId}`;
|
|
95
|
+
// Create and route the escalation
|
|
96
|
+
const escalationId = ulid();
|
|
97
|
+
const workspaceId = task.workspaceId ?? "";
|
|
98
|
+
escalationStore.createEscalation(escalationId, workspaceId, taskId, task.title, message, "auto", "normal", taskUrl);
|
|
99
|
+
// Mark dedupe AFTER successful persistence so failures can retry
|
|
100
|
+
delivered.set(dedupeKey, now);
|
|
101
|
+
// Route immediately — build the row inline to avoid a round-trip read
|
|
102
|
+
await routeEscalation({
|
|
103
|
+
id: escalationId,
|
|
104
|
+
workspaceId,
|
|
105
|
+
taskId,
|
|
106
|
+
title: task.title,
|
|
107
|
+
message,
|
|
108
|
+
source: "auto",
|
|
109
|
+
urgency: "normal",
|
|
110
|
+
status: "pending",
|
|
111
|
+
createdAt: new Date().toISOString(),
|
|
112
|
+
deliveredAt: null,
|
|
113
|
+
acknowledgedAt: null,
|
|
114
|
+
taskUrl,
|
|
115
|
+
});
|
|
116
|
+
logger.info({ taskId, escalationId, title: task.title }, "Auto-escalation created for idle standalone task");
|
|
117
|
+
}
|
|
118
|
+
/** Remove dedup entries older than DEDUP_TTL_MS. */
|
|
119
|
+
function pruneDelivered(delivered, now) {
|
|
120
|
+
for (const [key, timestamp] of delivered) {
|
|
121
|
+
if (now - timestamp >= DEDUP_TTL_MS) {
|
|
122
|
+
delivered.delete(key);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=escalation-auto.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"escalation-auto.js","sourceRoot":"","sources":["../../src/signals/escalation-auto.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Orphan reparenting — automatically reparent non-terminal children when
|
|
3
|
+
* a parent task reaches terminal state (complete/failed).
|
|
4
|
+
*
|
|
5
|
+
* Follows the SIGCHLD subscriber pattern: subscribes to domain events,
|
|
6
|
+
* detects orphan conditions, and reparents children to the grandparent.
|
|
7
|
+
* The root task (PID 1) is the ultimate adopter.
|
|
8
|
+
*/
|
|
9
|
+
import type { Disposable, PluginContext } from "@grackle-ai/core";
|
|
10
|
+
/**
|
|
11
|
+
* Create the orphan reparenting event-bus subscriber.
|
|
12
|
+
*
|
|
13
|
+
* Watches for parent tasks reaching terminal state and reparents their
|
|
14
|
+
* non-terminal children to the grandparent (or root task as ultimate adopter).
|
|
15
|
+
*
|
|
16
|
+
* @param ctx - Plugin context providing event-bus access.
|
|
17
|
+
* @returns A Disposable that unsubscribes and clears dedup state.
|
|
18
|
+
*/
|
|
19
|
+
export declare function createOrphanReparentSubscriber(ctx: PluginContext): Disposable;
|
|
20
|
+
/**
|
|
21
|
+
* Transfer ALL pipe subscriptions from a dead parent's sessions to the
|
|
22
|
+
* grandparent's active session. Called once per parent death (not per child).
|
|
23
|
+
*
|
|
24
|
+
* When a parent dies, all its pipe connections should move to the grandparent —
|
|
25
|
+
* like fd inheritance when a Unix process dies and init takes over.
|
|
26
|
+
*
|
|
27
|
+
* Exported so it can be called synchronously from completeTask() /
|
|
28
|
+
* killSessionAndCleanup() before sessions are cleaned up.
|
|
29
|
+
*/
|
|
30
|
+
export declare function transferAllPipeSubscriptions(deadParentTaskId: string, grandparentTaskId: string): void;
|
|
31
|
+
//# sourceMappingURL=orphan-reparent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"orphan-reparent.d.ts","sourceRoot":"","sources":["../../src/signals/orphan-reparent.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AASH,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAWlE;;;;;;;;GAQG;AACH,wBAAgB,8BAA8B,CAAC,GAAG,EAAE,aAAa,GAAG,UAAU,CAmC7E;AA+FD;;;;;;;;;GASG;AACH,wBAAgB,4BAA4B,CAC1C,gBAAgB,EAAE,MAAM,EACxB,iBAAiB,EAAE,MAAM,GACxB,IAAI,CAsDN"}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Orphan reparenting — automatically reparent non-terminal children when
|
|
3
|
+
* a parent task reaches terminal state (complete/failed).
|
|
4
|
+
*
|
|
5
|
+
* Follows the SIGCHLD subscriber pattern: subscribes to domain events,
|
|
6
|
+
* detects orphan conditions, and reparents children to the grandparent.
|
|
7
|
+
* The root task (PID 1) is the ultimate adopter.
|
|
8
|
+
*/
|
|
9
|
+
import { ROOT_TASK_ID, TASK_STATUS } from "@grackle-ai/common";
|
|
10
|
+
import { taskStore, sessionStore } from "@grackle-ai/database";
|
|
11
|
+
import { streamRegistry } from "@grackle-ai/core";
|
|
12
|
+
import { ensureAsyncDeliveryListener } from "@grackle-ai/core";
|
|
13
|
+
import { deliverSignalToTask } from "@grackle-ai/core";
|
|
14
|
+
import { logger } from "@grackle-ai/core";
|
|
15
|
+
/** Terminal task statuses that trigger orphan reparenting. */
|
|
16
|
+
const TERMINAL_TASK_STATUSES = new Set([
|
|
17
|
+
TASK_STATUS.COMPLETE,
|
|
18
|
+
TASK_STATUS.FAILED,
|
|
19
|
+
]);
|
|
20
|
+
/** How long (ms) to remember a processed parent before allowing re-processing. */
|
|
21
|
+
const DEDUP_TTL_MS = 3_600_000; // 1 hour
|
|
22
|
+
/**
|
|
23
|
+
* Create the orphan reparenting event-bus subscriber.
|
|
24
|
+
*
|
|
25
|
+
* Watches for parent tasks reaching terminal state and reparents their
|
|
26
|
+
* non-terminal children to the grandparent (or root task as ultimate adopter).
|
|
27
|
+
*
|
|
28
|
+
* @param ctx - Plugin context providing event-bus access.
|
|
29
|
+
* @returns A Disposable that unsubscribes and clears dedup state.
|
|
30
|
+
*/
|
|
31
|
+
export function createOrphanReparentSubscriber(ctx) {
|
|
32
|
+
/** Track processed parents to prevent duplicate reparenting: parentTaskId → timestamp. */
|
|
33
|
+
const processed = new Map();
|
|
34
|
+
const unsubscribe = ctx.subscribe((event) => {
|
|
35
|
+
if (event.type !== "task.completed" && event.type !== "task.updated") {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const parentTaskId = event.payload.taskId;
|
|
39
|
+
if (!parentTaskId) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
// Root task never completes — guard defensively
|
|
43
|
+
if (parentTaskId === ROOT_TASK_ID) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
// Fire-and-forget async handler — errors are logged, never thrown
|
|
47
|
+
(async () => {
|
|
48
|
+
try {
|
|
49
|
+
await handleParentTerminal(ctx, processed, parentTaskId);
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
logger.error({ err, parentTaskId }, "Orphan reparenting failed for parent task");
|
|
53
|
+
}
|
|
54
|
+
})().catch(() => { });
|
|
55
|
+
});
|
|
56
|
+
return {
|
|
57
|
+
dispose() {
|
|
58
|
+
unsubscribe();
|
|
59
|
+
processed.clear();
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Check if a parent task is terminal and reparent its non-terminal children.
|
|
65
|
+
*/
|
|
66
|
+
async function handleParentTerminal(ctx, processed, parentTaskId) {
|
|
67
|
+
const parentTask = taskStore.getTask(parentTaskId);
|
|
68
|
+
if (!parentTask) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
// Only trigger for terminal statuses
|
|
72
|
+
if (!TERMINAL_TASK_STATUSES.has(parentTask.status)) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
// Deduplication: skip if we already processed this parent recently
|
|
76
|
+
const now = Date.now();
|
|
77
|
+
const lastProcessed = processed.get(parentTaskId);
|
|
78
|
+
if (lastProcessed && now - lastProcessed < DEDUP_TTL_MS) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
// Mark as processed before doing work (prevents concurrent re-entry)
|
|
82
|
+
processed.set(parentTaskId, now);
|
|
83
|
+
// Determine the grandparent (or root task as ultimate adopter)
|
|
84
|
+
const grandparentId = parentTask.parentTaskId || ROOT_TASK_ID;
|
|
85
|
+
// Always transfer pipe fds from dead parent to grandparent, even when there
|
|
86
|
+
// are no orphaned tasks. ipc_spawn creates child sessions (not tasks), so
|
|
87
|
+
// pipe subscriptions can exist without corresponding child tasks.
|
|
88
|
+
transferAllPipeSubscriptions(parentTaskId, grandparentId);
|
|
89
|
+
// Get non-terminal children for reparenting
|
|
90
|
+
const orphans = taskStore.getOrphanedTasks(parentTaskId);
|
|
91
|
+
if (orphans.length === 0) {
|
|
92
|
+
// Evict stale dedup entries even when no reparenting needed
|
|
93
|
+
for (const [key, ts] of processed) {
|
|
94
|
+
if (now - ts > DEDUP_TTL_MS) {
|
|
95
|
+
processed.delete(key);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
logger.info({ parentTaskId, grandparentId, orphanCount: orphans.length, reason: parentTask.status }, "Reparenting orphaned children to grandparent");
|
|
101
|
+
// Reparent each orphan
|
|
102
|
+
for (const orphan of orphans) {
|
|
103
|
+
try {
|
|
104
|
+
taskStore.reparentTask(orphan.id, grandparentId);
|
|
105
|
+
ctx.emit("task.reparented", {
|
|
106
|
+
taskId: orphan.id,
|
|
107
|
+
oldParentTaskId: parentTaskId,
|
|
108
|
+
newParentTaskId: grandparentId,
|
|
109
|
+
workspaceId: orphan.workspaceId || "",
|
|
110
|
+
});
|
|
111
|
+
ctx.emit("task.updated", {
|
|
112
|
+
taskId: orphan.id,
|
|
113
|
+
workspaceId: orphan.workspaceId || "",
|
|
114
|
+
});
|
|
115
|
+
// Deliver [ADOPTED] signal to grandparent
|
|
116
|
+
const message = `[ADOPTED] Task "${orphan.title}" (${orphan.id}) was adopted from ` +
|
|
117
|
+
`terminated parent "${parentTask.title}" (${parentTask.id}). ` +
|
|
118
|
+
`The task is now your direct child. Use ipc_list_fds to see transferred pipe fds.`;
|
|
119
|
+
await deliverSignalToTask(grandparentId, "adopted", message);
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
logger.error({ err, orphanId: orphan.id, parentTaskId, grandparentId }, "Failed to reparent orphan — continuing with remaining children");
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// Evict stale dedup entries
|
|
126
|
+
for (const [key, ts] of processed) {
|
|
127
|
+
if (now - ts > DEDUP_TTL_MS) {
|
|
128
|
+
processed.delete(key);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Transfer ALL pipe subscriptions from a dead parent's sessions to the
|
|
134
|
+
* grandparent's active session. Called once per parent death (not per child).
|
|
135
|
+
*
|
|
136
|
+
* When a parent dies, all its pipe connections should move to the grandparent —
|
|
137
|
+
* like fd inheritance when a Unix process dies and init takes over.
|
|
138
|
+
*
|
|
139
|
+
* Exported so it can be called synchronously from completeTask() /
|
|
140
|
+
* killSessionAndCleanup() before sessions are cleaned up.
|
|
141
|
+
*/
|
|
142
|
+
export function transferAllPipeSubscriptions(deadParentTaskId, grandparentTaskId) {
|
|
143
|
+
const grandparentSessions = sessionStore.getActiveSessionsForTask(grandparentTaskId);
|
|
144
|
+
if (grandparentSessions.length === 0) {
|
|
145
|
+
logger.debug({ deadParentTaskId, grandparentTaskId }, "No active grandparent session — skipping pipe fd transfer");
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
const grandparentSessionId = grandparentSessions[0].id;
|
|
149
|
+
const parentSessions = sessionStore.listSessionsForTask(deadParentTaskId);
|
|
150
|
+
let transferred = 0;
|
|
151
|
+
for (const parentSession of parentSessions) {
|
|
152
|
+
const subs = streamRegistry.getSubscriptionsForSession(parentSession.id);
|
|
153
|
+
for (const sub of subs) {
|
|
154
|
+
const stream = streamRegistry.getStream(sub.streamId);
|
|
155
|
+
if (!stream?.name.startsWith("pipe:")) {
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
try {
|
|
159
|
+
streamRegistry.subscribe(sub.streamId, grandparentSessionId, sub.permission, sub.deliveryMode, sub.createdBySpawn);
|
|
160
|
+
streamRegistry.unsubscribe(sub.id);
|
|
161
|
+
if (sub.deliveryMode === "async") {
|
|
162
|
+
ensureAsyncDeliveryListener(grandparentSessionId);
|
|
163
|
+
}
|
|
164
|
+
transferred++;
|
|
165
|
+
}
|
|
166
|
+
catch (err) {
|
|
167
|
+
logger.warn({ err, stream: stream.name, deadParentTaskId }, "Failed to transfer pipe fd");
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (transferred > 0) {
|
|
172
|
+
logger.info({ deadParentTaskId, grandparentTaskId, grandparentSessionId, transferred }, "Transferred %d pipe fd(s) to grandparent session", transferred);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
//# sourceMappingURL=orphan-reparent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"orphan-reparent.js","sourceRoot":"","sources":["../../src/signals/orphan-reparent.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Disposable, PluginContext } from "@grackle-ai/core";
|
|
2
|
+
/**
|
|
3
|
+
* Create the SIGCHLD event-bus subscriber.
|
|
4
|
+
*
|
|
5
|
+
* Watches for child tasks whose latest session reaches a SIGCHLD-triggering
|
|
6
|
+
* status (idle or stopped) and delivers a notification to the parent task.
|
|
7
|
+
*
|
|
8
|
+
* @param ctx - Plugin context providing event-bus access.
|
|
9
|
+
* @returns A Disposable that unsubscribes and clears dedup state.
|
|
10
|
+
*/
|
|
11
|
+
export declare function createSigchldSubscriber(ctx: PluginContext): Disposable;
|
|
12
|
+
//# sourceMappingURL=sigchld.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sigchld.d.ts","sourceRoot":"","sources":["../../src/signals/sigchld.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAgClE;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,aAAa,GAAG,UAAU,CAgCtE"}
|