./bin/pi-loop:21:  --no-extensions        Pass --no-extensions to pi. Default: on.
./bin/pi-loop:22:  --allow-extensions     Do not pass --no-extensions.
./bin/pi-loop:56:    --no-extensions) DISABLE_EXTENSIONS=1; shift ;;
./bin/pi-loop:57:    --allow-extensions) DISABLE_EXTENSIONS=0; shift ;;
./bin/pi-loop:116:disable_extensions=$DISABLE_EXTENSIONS
./bin/pi-loop:150:  PI_ARGS+=(--no-extensions)
./bin/task-phase-loop:5:import { appendToolUsageEvent } from "../extensions/_shared/tool-usage";
./bin/task-phase-loop:10:  task-phase-loop --task-dir /home/sebas/work/tasks/T-123 --repo /path/repo [--max-passes 12] [--provider openai-codex] [--model gpt-5.4-mini] [--thinking low] [--allow-extensions]
./bin/task-phase-loop:39:      case "--allow-extensions": allowExtensions = true; break;
./bin/task-phase-loop:64:  if (allowExtensions) args.push("--allow-extensions");
./bin/task-phase-loop:88:  writeFileSync(path.join(loopDir, "run-meta.txt"), `created_at=${new Date().toISOString()}\ntask_dir=${taskDir}\nrepo=${repo}\nmax_passes=${maxPasses}\nprovider=${provider}\nmodel=${model}\nthinking=${thinking}\nallow_extensions=${allowExtensions}\n`);
./bin/pi-job-runner:21:pi --provider "$PI_PROVIDER" --model "$PI_MODEL" --thinking "$PI_THINKING" --no-extensions -p "$PROMPT" 2>&1 | tee -a "$JOB_DIR/output.log"
./bin/task-phase-run:5:import { getPiSpawnCommand } from "../extensions/_shared/pi-spawn.ts";
./bin/task-phase-run:10:  task-phase-run --task-dir /home/sebas/work/tasks/T-123 --repo /path/repo [--provider openai-codex] [--model gpt-5.4-mini] [--thinking low] [--allow-extensions]
./bin/task-phase-run:32:      case "--allow-extensions": allowExtensions = true; break;
./bin/task-phase-run:215:  if (!allowExtensions) args.push("--no-extensions");
./bin/task-phase-run:256:  writeFileSync(metaFile, `phase_before=${before.phase}\nprovider=${provider}\nmodel=${model}\nthinking=${thinking}\nallow_extensions=${allowExtensions}\nrepo=${repo}\ntask_dir=${taskDir}\n`);
./bin/ralph-loop-run:17:  --allow-extensions    Do not pass --no-extensions.
./bin/ralph-loop-run:46:    --allow-extensions) DISABLE_EXTENSIONS=0; shift ;;
./bin/ralph-loop-run:88:disable_extensions=$DISABLE_EXTENSIONS
./bin/ralph-loop-run:96:[[ "$DISABLE_EXTENSIONS" -eq 1 ]] && pi_args+=(--no-extensions)
./node_modules/typescript/lib/tr/diagnosticMessages.generated.json:211:  "Allow_imports_to_include_TypeScript_file_extensions_Requires_moduleResolution_bundler_and_either_noE_6407": "İçe aktarma işlemlerinin TypeScript dosya uzantılarını içermesine izin verin. '--moduleResolution bundler' ve '--noEmit' veya '--emitDeclarationOnly' ayarını gerektirir.",
./node_modules/typescript/lib/tr/diagnosticMessages.generated.json:537:  "Could_not_resolve_the_path_0_with_the_extensions_Colon_1_6231": "Uzantılara sahip '{0}' yolu çözümlenemedi: {1}.",
./node_modules/typescript/lib/tr/diagnosticMessages.generated.json:790:  "File_0_has_an_unsupported_extension_The_only_supported_extensions_are_1_6054": "'{0}' dosyası desteklenmeyen uzantıya sahip. Yalnızca şu uzantılar desteklenir: {1}.",
./node_modules/typescript/lib/tr/diagnosticMessages.generated.json:1332:  "Relative_import_paths_need_explicit_file_extensions_in_ECMAScript_imports_when_moduleResolution_is_n_2834": "'--moduleResolution' 'node16' veya 'nodenext' olduğunda, bağıl içe aktarma yollarının ECMAScript içe aktarmalarında açık dosya uzantılarına ihtiyacı vardır. İçe aktarma yoluna bir uzantı eklemeyi düşünün.",
./node_modules/typescript/lib/tr/diagnosticMessages.generated.json:1333:  "Relative_import_paths_need_explicit_file_extensions_in_ECMAScript_imports_when_moduleResolution_is_n_2835": "Göreli içe aktarma yolları, '--moduleResolution' 'node16' veya 'nodenext' olduğunda ECMAScript içeri aktarmalarında açık dosya uzantılarına ihtiyaç duyar. Şunu mu demek istediniz: '{0}'?",
./node_modules/typescript/lib/tr/diagnosticMessages.generated.json:1431:  "Rewrite_ts_tsx_mts_and_cts_file_extensions_in_relative_import_paths_to_their_JavaScript_equivalent_i_6421": "'.ts', '.tsx', '.mts' ve '.cts' dosya uzantılarını, çıkış dosyalarındaki JavaScript eşdeğerlerine göreli içeri aktarma yollarında yeniden yazın.",
./node_modules/typescript/lib/tr/diagnosticMessages.generated.json:1439:  "Searching_all_ancestor_node_modules_directories_for_fallback_extensions_Colon_0_6418": "Geri dönüş uzantıları için tüm üst node_modules dizinleri aranıyor: {0}.",
./node_modules/typescript/lib/tr/diagnosticMessages.generated.json:1440:  "Searching_all_ancestor_node_modules_directories_for_preferred_extensions_Colon_0_6417": "Tercih edilen uzantılar için tüm üst node_modules dizinleri aranıyor: {0}.",
./node_modules/typescript/lib/ja/diagnosticMessages.generated.json:211:  "Allow_imports_to_include_TypeScript_file_extensions_Requires_moduleResolution_bundler_and_either_noE_6407": "TypeScript ファイル拡張子を含めるインポートを許可してください。'--moduleResolution bundler' と '--noEmit' または '--emitDeclarationOnly' のいずれかを設定する必要があります。",
./node_modules/typescript/lib/ja/diagnosticMessages.generated.json:537:  "Could_not_resolve_the_path_0_with_the_extensions_Colon_1_6231": "次の拡張子を持つパス '{0}' を解決できませんでした: {1}。",
./node_modules/typescript/lib/ja/diagnosticMessages.generated.json:790:  "File_0_has_an_unsupported_extension_The_only_supported_extensions_are_1_6054": "ファイル '{0}' はサポートされていない拡張子を含んでいます。サポートされている拡張子は {1} のみです。",
./node_modules/typescript/lib/ja/diagnosticMessages.generated.json:1332:  "Relative_import_paths_need_explicit_file_extensions_in_ECMAScript_imports_when_moduleResolution_is_n_2834": "相対インポート パスでは、'--moduleResolution' が 'node16' または 'nodenext' である場合、ECMAScript インポートに明示的なファイル拡張子が必要です。インポート パスに拡張機能を追加することを検討してください。",
./node_modules/typescript/lib/ja/diagnosticMessages.generated.json:1333:  "Relative_import_paths_need_explicit_file_extensions_in_ECMAScript_imports_when_moduleResolution_is_n_2835": "相対インポート パスでは、'--moduleResolution' が 'node16' または 'nodenext' である場合、ECMAScript インポートに明示的なファイル拡張子が必要です。'{0}' を意図していましたか?",
./node_modules/typescript/lib/ja/diagnosticMessages.generated.json:1431:  "Rewrite_ts_tsx_mts_and_cts_file_extensions_in_relative_import_paths_to_their_JavaScript_equivalent_i_6421": "相対インポート パスの '.ts'、'.tsx'、'.mts'、および '.cts' ファイル拡張子を、出力ファイルの JavaScript と同等の拡張子に書き換えます。",
./node_modules/typescript/lib/ja/diagnosticMessages.generated.json:1439:  "Searching_all_ancestor_node_modules_directories_for_fallback_extensions_Colon_0_6418": "すべての先祖 node_modules ディレクトリでフォールバック拡張子を検索しています: {0}。",
./node_modules/typescript/lib/ja/diagnosticMessages.generated.json:1440:  "Searching_all_ancestor_node_modules_directories_for_preferred_extensions_Colon_0_6417": "すべての先祖 node_modules ディレクトリで優先する拡張子を検索しています: {0}。",
./node_modules/typescript/lib/typescript.d.ts:1152:                 * The host's additional supported .js file extensions
./node_modules/typescript/lib/typescript.d.ts:2904:            readDirectory(path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[], depth?: number): string[];
./node_modules/typescript/lib/typescript.d.ts:3137:            /** Count of file extensions seen in the project. */
./node_modules/typescript/lib/typescript.d.ts:3646:            readDirectory(rootDir: string, extensions: readonly string[], excludes: readonly string[] | undefined, includes: readonly string[] | undefined, depth?: number): string[];
./node_modules/typescript/lib/typescript.d.ts:5991:        readDirectory(rootDir: string, extensions: readonly string[], excludes: readonly string[] | undefined, includes: readonly string[], depth?: number): readonly string[];
./node_modules/typescript/lib/typescript.d.ts:7199:         * Used on extensions that doesn't define the ScriptKind but the content defines it.
./node_modules/typescript/lib/typescript.d.ts:7200:         * Deferred extensions are going to be included in all project contexts.
./node_modules/typescript/lib/typescript.d.ts:7360:        readDirectory?(rootDir: string, extensions: readonly string[], excludes: readonly string[] | undefined, includes: readonly string[], depth?: number): string[];
./node_modules/typescript/lib/typescript.d.ts:8468:        readDirectory(path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[], depth?: number): string[];
./node_modules/typescript/lib/typescript.d.ts:9835:        readDirectory?(path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[], depth?: number): string[];
./node_modules/typescript/lib/typescript.d.ts:9898:        readDirectory(path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[], depth?: number): string[];
./node_modules/typescript/lib/typescript.d.ts:10081:        readDirectory?(path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[], depth?: number): string[];
./node_modules/typescript/lib/es/diagnosticMessages.generated.json:211:  "Allow_imports_to_include_TypeScript_file_extensions_Requires_moduleResolution_bundler_and_either_noE_6407": "Permitir que las importaciones incluyan extensiones de archivo TypeScript. Requiere que se establezca \"--moduleResolution bundler\" y \"--noEmit\" o \"--emitDeclarationOnly\".",
./node_modules/typescript/lib/es/diagnosticMessages.generated.json:537:  "Could_not_resolve_the_path_0_with_the_extensions_Colon_1_6231": "No se pudo resolver la ruta de acceso \"{0}\" con las extensiones: {1}.",
./node_modules/typescript/lib/es/diagnosticMessages.generated.json:790:  "File_0_has_an_unsupported_extension_The_only_supported_extensions_are_1_6054": "El archivo \"{0}\" tiene una extensión no compatible. Las únicas extensiones compatibles son {1}.",
./node_modules/typescript/lib/es/diagnosticMessages.generated.json:1332:  "Relative_import_paths_need_explicit_file_extensions_in_ECMAScript_imports_when_moduleResolution_is_n_2834": "Las rutas de acceso de importación relativas necesitan extensiones de archivo explícitas en las importaciones ECMAScript cuando “--moduleResolution” es “node16” o “nodenext”. Considere la posibilidad de agregar una extensión a la ruta de acceso de importación.",
./node_modules/typescript/lib/es/diagnosticMessages.generated.json:1333:  "Relative_import_paths_need_explicit_file_extensions_in_ECMAScript_imports_when_moduleResolution_is_n_2835": "Las rutas de acceso de importación relativas necesitan extensiones de archivo explícitas en las importaciones ECMAScript cuando “--moduleResolution” es “node16” o “nodenext”. ¿Quiso decir “{0}”?",
./node_modules/typescript/lib/es/diagnosticMessages.generated.json:1431:  "Rewrite_ts_tsx_mts_and_cts_file_extensions_in_relative_import_paths_to_their_JavaScript_equivalent_i_6421": "Vuelva a escribir las extensiones de archivo \".ts\", \".tsx\", \".mts\" y \".cts\" en rutas de acceso de importación relativas a su equivalente de JavaScript en los archivos de salida.",
./node_modules/typescript/lib/es/diagnosticMessages.generated.json:1439:  "Searching_all_ancestor_node_modules_directories_for_fallback_extensions_Colon_0_6418": "Buscando extensiones de reserva en todos los directorios de node_modules antecesores: {0}.",
./node_modules/typescript/lib/es/diagnosticMessages.generated.json:1440:  "Searching_all_ancestor_node_modules_directories_for_preferred_extensions_Colon_0_6417": "Buscando extensiones preferidas en todos los directorios de node_modules antecesores: {0}.",
./node_modules/typescript/lib/de/diagnosticMessages.generated.json:211:  "Allow_imports_to_include_TypeScript_file_extensions_Requires_moduleResolution_bundler_and_either_noE_6407": "Importe dürfen TypeScript-Dateierweiterungen enthalten. Erfordert die Festlegung von \"--moduleResolution bundler\" und \"--noEmit\" oder \"--emitDeclarationOnly\".",
./node_modules/typescript/lib/de/diagnosticMessages.generated.json:537:  "Could_not_resolve_the_path_0_with_the_extensions_Colon_1_6231": "Der Pfad \"{0}\" mit den Erweiterungen konnte nicht aufgelöst werden: {1}.",
./node_modules/typescript/lib/de/diagnosticMessages.generated.json:790:  "File_0_has_an_unsupported_extension_The_only_supported_extensions_are_1_6054": "Die Datei \"{0}\" weist eine nicht unterstützte Erweiterung auf. Es werden nur die folgenden Erweiterungen unterstützt: {1}.",
./node_modules/typescript/lib/de/diagnosticMessages.generated.json:1332:  "Relative_import_paths_need_explicit_file_extensions_in_ECMAScript_imports_when_moduleResolution_is_n_2834": "Relative Importpfade erfordern explizite Dateierweiterungen in ECMAScript-Importen, wenn „--moduleResolution“ „node16“ oder „nodenext“ ist. Erwägen Sie, dem Importpfad eine Erweiterung hinzuzufügen.",
./node_modules/typescript/lib/de/diagnosticMessages.generated.json:1333:  "Relative_import_paths_need_explicit_file_extensions_in_ECMAScript_imports_when_moduleResolution_is_n_2835": "Relative Importpfade erfordern explizite Dateierweiterungen in ECMAScript-Importen, wenn „--moduleResolution“ „node16“ oder „nodenext“ ist. Meinten Sie \"{0}\"?",
./node_modules/typescript/lib/de/diagnosticMessages.generated.json:1431:  "Rewrite_ts_tsx_mts_and_cts_file_extensions_in_relative_import_paths_to_their_JavaScript_equivalent_i_6421": "Schreiben Sie die Dateiendungen '.ts', '.tsx', '.mts' und '.cts' in relativen Importpfaden in ihren JavaScript-Äquivalenten in den Ausgabedateien um.",
./node_modules/typescript/lib/de/diagnosticMessages.generated.json:1439:  "Searching_all_ancestor_node_modules_directories_for_fallback_extensions_Colon_0_6418": "Alle node_modules-Vorgängerverzeichnisse werden nach Fallbackerweiterungen durchsucht: {0}.",
./node_modules/typescript/lib/de/diagnosticMessages.generated.json:1440:  "Searching_all_ancestor_node_modules_directories_for_preferred_extensions_Colon_0_6417": "Alle node_modules-Vorgängerverzeichnisse werden nach bevorzugten Erweiterungen durchsucht: {0}.",
./node_modules/typescript/lib/lib.es5.d.ts:304:    // Non-standard extensions
./node_modules/typescript/lib/lib.es5.d.ts:519:    // IE extensions
./node_modules/typescript/lib/lib.es5.d.ts:1020:    // Non-standard extensions
./node_modules/typescript/lib/lib.es5.d.ts:1032:    // Non-standard extensions
./node_modules/typescript/lib/pt-br/diagnosticMessages.generated.json:211:  "Allow_imports_to_include_TypeScript_file_extensions_Requires_moduleResolution_bundler_and_either_noE_6407": "Permitir que as importações incluam extensões de arquivo TypeScript. Requer a definição de '--moduleResolution bundler' e '--noEmit' ou '--emitDeclarationOnly'.",
./node_modules/typescript/lib/pt-br/diagnosticMessages.generated.json:537:  "Could_not_resolve_the_path_0_with_the_extensions_Colon_1_6231": "Não foi possível resolver o caminho '{0}' com as extensões: {1}.",
./node_modules/typescript/lib/pt-br/diagnosticMessages.generated.json:790:  "File_0_has_an_unsupported_extension_The_only_supported_extensions_are_1_6054": "O arquivo '{0}' tem uma extensão sem suporte. As únicas extensões com suporte são {1}.",
./node_modules/typescript/lib/pt-br/diagnosticMessages.generated.json:1332:  "Relative_import_paths_need_explicit_file_extensions_in_ECMAScript_imports_when_moduleResolution_is_n_2834": "Os caminhos de importação relativos precisam de extensões de arquivo explícitas nas importações do ECMAScript quando '--moduleResolution' for 'node16' ou 'nodenext'. Considere adicionar uma extensão ao caminho de importação.",
./node_modules/typescript/lib/pt-br/diagnosticMessages.generated.json:1333:  "Relative_import_paths_need_explicit_file_extensions_in_ECMAScript_imports_when_moduleResolution_is_n_2835": "Os caminhos de importação relativos precisam de extensões de arquivo explícitas nas importações do ECMAScript quando '--moduleResolution' for 'node16' ou 'nodenext'. Você quis dizer '{0}'?",
./node_modules/typescript/lib/pt-br/diagnosticMessages.generated.json:1431:  "Rewrite_ts_tsx_mts_and_cts_file_extensions_in_relative_import_paths_to_their_JavaScript_equivalent_i_6421": "Reescreva as extensões de arquivo \".ts\", \".tsx\", \".mts\" e \".cts\" em caminhos de importação relativos para seu equivalente em JavaScript nos arquivos de saída.",
./node_modules/typescript/lib/pt-br/diagnosticMessages.generated.json:1439:  "Searching_all_ancestor_node_modules_directories_for_fallback_extensions_Colon_0_6418": "Pesquisando todos os diretórios node_modules ancestrais em busca de extensões de fallback: {0}.",
./node_modules/typescript/lib/pt-br/diagnosticMessages.generated.json:1440:  "Searching_all_ancestor_node_modules_directories_for_preferred_extensions_Colon_0_6417": "Pesquisando todos os diretórios node_modules ancestrais em busca de extensões preferenciais: {0}.",
./node_modules/typescript/lib/pl/diagnosticMessages.generated.json:211:  "Allow_imports_to_include_TypeScript_file_extensions_Requires_moduleResolution_bundler_and_either_noE_6407": "Zezwalaj na importowanie w celu uwzględnienia rozszerzeń plików TypeScript. Wymaga ustawienia elementu „--moduleResolution bundler” i „--noEmit” lub „--emitDeclarationOnly”.",
./node_modules/typescript/lib/pl/diagnosticMessages.generated.json:537:  "Could_not_resolve_the_path_0_with_the_extensions_Colon_1_6231": "Nie można rozpoznać ścieżki „{0}” z rozszerzeniami: {1}.",
./node_modules/typescript/lib/pl/diagnosticMessages.generated.json:790:  "File_0_has_an_unsupported_extension_The_only_supported_extensions_are_1_6054": "Plik „{0}” ma nieobsługiwane rozszerzenie. Obsługiwane są tylko rozszerzenia {1}.",
./node_modules/typescript/lib/pl/diagnosticMessages.generated.json:1332:  "Relative_import_paths_need_explicit_file_extensions_in_ECMAScript_imports_when_moduleResolution_is_n_2834": "Względne ścieżki importu wymagają jawnych rozszerzeń plików w importach ECMAScript, gdy element „--moduleResolution” ma wartość „node16” lub „nodenext”. Zastanów się nad dodaniem rozszerzenia do ścieżki importu.",
./node_modules/typescript/lib/pl/diagnosticMessages.generated.json:1333:  "Relative_import_paths_need_explicit_file_extensions_in_ECMAScript_imports_when_moduleResolution_is_n_2835": "Względne ścieżki importu wymagają jawnych rozszerzeń plików w importach ECMAScript, gdy element „--moduleResolution” ma wartość „node16” lub „nodenext”. Czy chodziło Ci o „{0}”?",
./node_modules/typescript/lib/pl/diagnosticMessages.generated.json:1431:  "Rewrite_ts_tsx_mts_and_cts_file_extensions_in_relative_import_paths_to_their_JavaScript_equivalent_i_6421": "Ponownie zapisz rozszerzenia plików „.ts”, „.tsx”, „.mts” i „.cts” we względnych ścieżkach importu do ich odpowiedników języka JavaScript w plikach wyjściowych.",
./node_modules/typescript/lib/pl/diagnosticMessages.generated.json:1439:  "Searching_all_ancestor_node_modules_directories_for_fallback_extensions_Colon_0_6418": "Przeszukiwanie wszystkich katalogów nadrzędnych node_modules w poszukiwaniu rozszerzeń rezerwowych: {0}.",
./node_modules/typescript/lib/pl/diagnosticMessages.generated.json:1440:  "Searching_all_ancestor_node_modules_directories_for_preferred_extensions_Colon_0_6417": "Wyszukiwanie preferowanych rozszerzeń we wszystkich katalogach nadrzędnych node_modules: {0}.",
./node_modules/typescript/lib/_tsc.js:5229:    function readDirectory(path, extensions, excludes, includes, depth) {
./node_modules/typescript/lib/_tsc.js:5230:      return matchFiles(path, extensions, excludes, includes, useCaseSensitiveFileNames2, process.cwd(), depth, getAccessibleFileSystemEntries, realpath);
./node_modules/typescript/lib/_tsc.js:5338:function fileExtensionIsOneOf(path, extensions) {
./node_modules/typescript/lib/_tsc.js:5339:  for (const extension of extensions) {
./node_modules/typescript/lib/_tsc.js:5410:function getBaseFileName(path, extensions, ignoreCase) {
./node_modules/typescript/lib/_tsc.js:5416:  const extension = extensions !== void 0 && ignoreCase !== void 0 ? getAnyExtensionFromPath(name, extensions, ignoreCase) : void 0;
./node_modules/typescript/lib/_tsc.js:5428:function getAnyExtensionFromPathWorker(path, extensions, stringEqualityComparer) {
./node_modules/typescript/lib/_tsc.js:5429:  if (typeof extensions === "string") {
./node_modules/typescript/lib/_tsc.js:5430:    return tryGetExtensionFromPath(path, extensions, stringEqualityComparer) || "";
./node_modules/typescript/lib/_tsc.js:5432:  for (const extension of extensions) {
./node_modules/typescript/lib/_tsc.js:5438:function getAnyExtensionFromPath(path, extensions, ignoreCase) {
./node_modules/typescript/lib/_tsc.js:5439:  if (extensions) {
./node_modules/typescript/lib/_tsc.js:5440:    return getAnyExtensionFromPathWorker(removeTrailingDirectorySeparator(path), extensions, ignoreCase ? equateStringsCaseInsensitive : equateStringsCaseSensitive);
./node_modules/typescript/lib/_tsc.js:5631:function changeAnyExtension(path, ext, extensions, ignoreCase) {
./node_modules/typescript/lib/_tsc.js:5632:  const pathext = extensions !== void 0 && ignoreCase !== void 0 ? getAnyExtensionFromPath(path, extensions, ignoreCase) : getAnyExtensionFromPath(path);
./node_modules/typescript/lib/_tsc.js:6789:  Relative_import_paths_need_explicit_file_extensions_in_ECMAScript_imports_when_moduleResolution_is_node16_or_nodenext_Consider_adding_an_extension_to_the_import_path: diag(2834, 1 /* Error */, "Relative_import_paths_need_explicit_file_extensions_in_ECMAScript_imports_when_moduleResolution_is_n_2834", "Relative import paths need explicit file extensions in ECMAScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Consider adding an extension to the import path."),
./node_modules/typescript/lib/_tsc.js:6790:  Relative_import_paths_need_explicit_file_extensions_in_ECMAScript_imports_when_moduleResolution_is_node16_or_nodenext_Did_you_mean_0: diag(2835, 1 /* Error */, "Relative_import_paths_need_explicit_file_extensions_in_ECMAScript_imports_when_moduleResolution_is_n_2835", "Relative import paths need explicit file extensions in ECMAScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Did you mean '{0}'?"),
./node_modules/typescript/lib/_tsc.js:7057:  File_0_has_an_unsupported_extension_The_only_supported_extensions_are_1: diag(6054, 1 /* Error */, "File_0_has_an_unsupported_extension_The_only_supported_extensions_are_1_6054", "File '{0}' has an unsupported extension. The only supported extensions are {1}."),
./node_modules/typescript/lib/_tsc.js:7252:  Could_not_resolve_the_path_0_with_the_extensions_Colon_1: diag(6231, 1 /* Error */, "Could_not_resolve_the_path_0_with_the_extensions_Colon_1_6231", "Could not resolve the path '{0}' with the extensions: {1}."),
./node_modules/typescript/lib/_tsc.js:7388:  Allow_imports_to_include_TypeScript_file_extensions_Requires_moduleResolution_bundler_and_either_noEmit_or_emitDeclarationOnly_to_be_set: diag(6407, 3 /* Message */, "Allow_imports_to_include_TypeScript_file_extensions_Requires_moduleResolution_bundler_and_either_noE_6407", "Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set."),
./node_modules/typescript/lib/_tsc.js:7398:  Searching_all_ancestor_node_modules_directories_for_preferred_extensions_Colon_0: diag(6417, 3 /* Message */, "Searching_all_ancestor_node_modules_directories_for_preferred_extensions_Colon_0_6417", "Searching all ancestor node_modules directories for preferred extensions: {0}."),
./node_modules/typescript/lib/_tsc.js:7399:  Searching_all_ancestor_node_modules_directories_for_fallback_extensions_Colon_0: diag(6418, 3 /* Message */, "Searching_all_ancestor_node_modules_directories_for_fallback_extensions_Colon_0_6418", "Searching all ancestor node_modules directories for fallback extensions: {0}."),
./node_modules/typescript/lib/_tsc.js:7402:  Rewrite_ts_tsx_mts_and_cts_file_extensions_in_relative_import_paths_to_their_JavaScript_equivalent_in_output_files: diag(6421, 3 /* Message */, "Rewrite_ts_tsx_mts_and_cts_file_extensions_in_relative_import_paths_to_their_JavaScript_equivalent_i_6421", "Rewrite '.ts', '.tsx', '.mts', and '.cts' file extensions in relative import paths to their JavaScript equivalent in output files."),
./node_modules/typescript/lib/_tsc.js:18463:function matchFiles(path, extensions, excludes, includes, useCaseSensitiveFileNames2, currentDirectory, depth, getFileSystemEntries, realpath) {
./node_modules/typescript/lib/_tsc.js:18485:      if (extensions && !fileExtensionIsOneOf(name, extensions)) continue;
./node_modules/typescript/lib/_tsc.js:18569:var extensionsNotSupportingExtensionlessResolution = [".mts" /* Mts */, ".d.mts" /* Dmts */, ".mjs" /* Mjs */, ".cts" /* Cts */, ".d.cts" /* Dcts */, ".cjs" /* Cjs */];
./node_modules/typescript/lib/_tsc.js:18577:  const extensions = [
./node_modules/typescript/lib/_tsc.js:18581:  return extensions;
./node_modules/typescript/lib/_tsc.js:18602:  return firstDefined(imports, ({ text }) => pathIsRelative(text) && !fileExtensionIsOneOf(text, extensionsNotSupportingExtensionlessResolution) ? hasExtension2(text) : void 0) || false;
./node_modules/typescript/lib/_tsc.js:18631:        if (fileExtensionIsOneOf(specifier.text, extensionsNotSupportingExtensionlessResolution)) {
./node_modules/typescript/lib/_tsc.js:18686:var extensionsToRemove = [".d.ts" /* Dts */, ".d.mts" /* Dmts */, ".d.cts" /* Dcts */, ".mjs" /* Mjs */, ".mts" /* Mts */, ".cjs" /* Cjs */, ".cts" /* Cts */, ".ts" /* Ts */, ".js" /* Js */, ".tsx" /* Tsx */, ".jsx" /* Jsx */, ".json" /* Json */];
./node_modules/typescript/lib/_tsc.js:18688:  for (const ext of extensionsToRemove) {
./node_modules/typescript/lib/_tsc.js:18706:    extensionsToRemove,
./node_modules/typescript/lib/_tsc.js:18763:  return find(extensionsToRemove, (e) => fileExtensionIs(path, e));
./node_modules/typescript/lib/_tsc.js:37360:    description: Diagnostics.Allow_imports_to_include_TypeScript_file_extensions_Requires_moduleResolution_bundler_and_either_noEmit_or_emitDeclarationOnly_to_be_set,


bash

cd /home/sebas/pi-config && find extensions -maxdepth 2 -type f | sort

ls -la /home/sebas/.pi/agent/extensions && find /home/sebas/.pi/agent/extensions -maxdepth 2 -type f | sort


extensions/questionnaire.ts
extensions/session-memory-dispatcher.ts
extensions/session-switch/index.ts
extensions/session-switch/picker.ts
extensions/session-switch/relaunch.ts
extensions/_shared/pi-spawn.ts
extensions/_shared/tool-usage.ts
extensions/subagent/agents.ts
extensions/subagent/index.ts


total 28
drwxrwxr-x 3 sebas sebas  4096 Apr 21 16:22 .
drwxrwxr-x 8 sebas sebas  4096 Jun  9 10:03 ..
-rw-rw-r-- 1 sebas sebas 12827 Apr 16 17:41 questionnaire.ts
drwxrwxr-x 2 sebas sebas  4096 Apr 16 17:25 subagent
/home/sebas/.pi/agent/extensions/questionnaire.ts
/home/sebas/.pi/agent/extensions/subagent/agents.ts
/home/sebas/.pi/agent/extensions/subagent/index.ts


**Investigating extension installation**

I'm noticing that only some extensions are installed. It seems like I might need to copy the extension files manually. I could implement these directly in the .pi/agent/extensions folder and possibly source them in pi-config. However, updating pi-config and then syncing seems like the better approach. I want to keep everything minimal while checking the subagent extension pattern and see if I can inspect the announcement hook API events thoroughly.


/**
 * Subagent Tool - Delegate tasks to specialized agents
 *
 * Spawns a separate `pi` process for each subagent invocation,
 * giving it an isolated context window.
 *
 * Supports three modes:
 *   - Single: { agent: "name", task: "..." }
 *   - Parallel: { tasks: [{ agent: "name", task: "..." }, ...] }
 *   - Chain: { chain: [{ agent: "name", task: "... {previous} ..." }, ...] }
 *
 * Uses JSON mode to capture structured output from subagents.
 */

import { spawn } from "node:child_process";
import * as fs from "node:fs";
import * as os from "node:os";
import * as path from "node:path";
import type { AgentToolResult } from "@mariozechner/pi-agent-core";
import type { Message } from "@mariozechner/pi-ai";
import { type ExtensionAPI, getMarkdownTheme, withFileMutationQueue } from "@mariozechner/pi-coding-agent";
import { Container, Markdown, Spacer, Text } from "@mariozechner/pi-tui";
import { Type } from "@sinclair/typebox";
import { appendToolUsageEvent } from "../_shared/tool-usage";
import { type AgentConfig, type AgentScope, discoverAgents } from "./agents.js";

const MAX_PARALLEL_TASKS = 8;
const MAX_CONCURRENCY = 4;
const COLLAPSED_ITEM_COUNT = 10;

function formatTokens(count: number): string {
	if (count < 1000) return count.toString();
	if (count < 10000) return `${(count / 1000).toFixed(1)}k`;
	if (count < 1000000) return `${Math.round(count / 1000)}k`;
	return `${(count / 1000000).toFixed(1)}M`;
}

function formatUsageStats(
	usage: {
		input: number;
		output: number;
		cacheRead: number;
		cacheWrite: number;
		cost: number;
		contextTokens?: number;
		turns?: number;
	},
	model?: string,
): string {
	const parts: string[] = [];
	if (usage.turns) parts.push(`${usage.turns} turn${usage.turns > 1 ? "s" : ""}`);
	if (usage.input) parts.push(`↑${formatTokens(usage.input)}`);
	if (usage.output) parts.push(`↓${formatTokens(usage.output)}`);
	if (usage.cacheRead) parts.push(`R${formatTokens(usage.cacheRead)}`);
	if (usage.cacheWrite) parts.push(`W${formatTokens(usage.cacheWrite)}`);
	if (usage.cost) parts.push(`$${usage.cost.toFixed(4)}`);
	if (usage.contextTokens && usage.contextTokens > 0) {
		parts.push(`ctx:${formatTokens(usage.contextTokens)}`);
	}
	if (model) parts.push(model);
	return parts.join(" ");
}

function formatToolCall(
	toolName: string,
	args: Record<string, unknown>,
	themeFg: (color: any, text: string) => string,
): string {
	const shortenPath = (p: string) => {
		const home = os.homedir();
		return p.startsWith(home) ? `~${p.slice(home.length)}` : p;
	};

	switch (toolName) {
		case "bash": {
			const command = (args.command as string) || "...";
			const preview = command.length > 60 ? `${command.slice(0, 60)}...` : command;
			return themeFg("muted", "$ ") + themeFg("toolOutput", preview);
		}
		case "read": {
			const rawPath = (args.file_path || args.path || "...") as string;
			const filePath = shortenPath(rawPath);
			const offset = args.offset as number | undefined;
			const limit = args.limit as number | undefined;
			let text = themeFg("accent", filePath);
			if (offset !== undefined || limit !== undefined) {
				const startLine = offset ?? 1;
				const endLine = limit !== undefined ? startLine + limit - 1 : "";
				text += themeFg("warning", `:${startLine}${endLine ? `-${endLine}` : ""}`);
			}
			return themeFg("muted", "read ") + text;
		}
		case "write": {
			const rawPath = (args.file_path || args.path || "...") as string;
			const filePath = shortenPath(rawPath);
			const content = (args.content || "") as string;
			const lines = content.split("\n").length;
			let text = themeFg("muted", "write ") + themeFg("accent", filePath);
			if (lines > 1) text += themeFg("dim", ` (${lines} lines)`);
			return text;
		}
		case "edit": {
			const rawPath = (args.file_path || args.path || "...") as string;
			return themeFg("muted", "edit ") + themeFg("accent", shortenPath(rawPath));
		}
		case "ls": {
			const rawPath = (args.path || ".") as string;
			return themeFg("muted", "ls ") + themeFg("accent", shortenPath(rawPath));
		}
		case "find": {
			const pattern = (args.pattern || "*") as string;
			const rawPath = (args.path || ".") as string;
			return themeFg("muted", "find ") + themeFg("accent", pattern) + themeFg("dim", ` in ${shortenPath(rawPath)}`);
		}
		case "grep": {
			const pattern = (args.pattern || "") as string;
			const rawPath = (args.path || ".") as string;
			return (
				themeFg("muted", "grep ") +
				themeFg("accent", `/${pattern}/`) +
				themeFg("dim", ` in ${shortenPath(rawPath)}`)
			);
		}
		default: {
			const argsStr = JSON.stringify(args);
			const preview = argsStr.length > 50 ? `${argsStr.slice(0, 50)}...` : argsStr;
			return themeFg("accent", toolName) + themeFg("dim", ` ${preview}`);
		}
	}
}

interface UsageStats {
	input: number;
	output: number;
	cacheRead: number;
	cacheWrite: number;
	cost: number;
	contextTokens: number;
	turns: number;
}

interface SingleResult {
	agent: string;
	agentSource: "user" | "project" | "unknown";
	task: string;
	exitCode: number;
	messages: Message[];
	stderr: string;
	usage: UsageStats;
	model?: string;
	stopReason?: string;
	errorMessage?: string;
	step?: number;
}

interface SubagentDetails {
	mode: "single" | "parallel" | "chain";
	agentScope: AgentScope;
	projectAgentsDir: string | null;
	results: SingleResult[];
}

function getFinalOutput(messages: Message[]): string {
	for (let i = messages.length - 1; i >= 0; i--) {
		const msg = messages[i];
		if (msg.role === "assistant") {
			for (const part of msg.content) {
				if (part.type === "text") return part.text;
			}
		}
	}
	return "";
}

type DisplayItem = { type: "text"; text: string } | { type: "toolCall"; name: string; args: Record<string, any> };

function getDisplayItems(messages: Message[]): DisplayItem[] {
	const items: DisplayItem[] = [];
	for (const msg of messages) {
		if (msg.role === "assistant") {
			for (const part of msg.content) {
				if (part.type === "text") items.push({ type: "text", text: part.text });
				else if (part.type === "toolCall") items.push({ type: "toolCall", name: part.name, args: part.arguments });
			}
		}
	}
	return items;
}

async function mapWithConcurrencyLimit<TIn, TOut>(
	items: TIn[],
	concurrency: number,
	fn: (item: TIn, index: number) => Promise<TOut>,
): Promise<TOut[]> {
	if (items.length === 0) return [];
	const limit = Math.max(1, Math.min(concurrency, items.length));
	const results: TOut[] = new Array(items.length);
	let nextIndex = 0;
	const workers = new Array(limit).fill(null).map(async () => {
		while (true) {
			const current = nextIndex++;
			if (current >= items.length) return;
			results[current] = await fn(items[current], current);
		}
	});
	await Promise.all(workers);
	return results;
}

function getSubagentTmpBaseDir(): string {
	return "/tmp/agent-subagent";
}

async function writePromptToTempFile(agentName: string, prompt: string): Promise<{ dir: string; filePath: string }> {
	const tmpBaseDir = getSubagentTmpBaseDir();
	await fs.promises.mkdir(tmpBaseDir, { recursive: true, mode: 0o700 });
	const tmpDir = await fs.promises.mkdtemp(path.join(tmpBaseDir, "agent-subagent-"));
	const safeName = agentName.replace(/[^\w.-]+/g, "_");
	const filePath = path.join(tmpDir, `prompt-${safeName}.md`);
	await withFileMutationQueue(filePath, async () => {
		await fs.promises.writeFile(filePath, prompt, { encoding: "utf-8", mode: 0o600 });
	});
	return { dir: tmpDir, filePath };
}

function getPiInvocation(args: string[]): { command: string; args: string[] } {
	const currentScript = process.argv[1];
	const isBunVirtualScript = currentScript?.startsWith("/$bunfs/root/");
	if (currentScript && !isBunVirtualScript && fs.existsSync(currentScript)) {
		return { command: process.execPath, args: [currentScript, ...args] };
	}

	const execName = path.basename(process.execPath).toLowerCase();
	const isGenericRuntime = /^(node|bun)(\.exe)?$/.test(execName);
	if (!isGenericRuntime) {
		return { command: process.execPath, args };
	}

	return { command: "pi", args };
}

type OnUpdateCallback = (partial: AgentToolResult<SubagentDetails>) => void;

type AgentRequest = {
	agent?: string;
	description?: string;
	model?: string;
	tools?: string[];
	systemPrompt?: string;
};

function resolveAgent(agents: AgentConfig[], request: AgentRequest): { agent?: AgentConfig; error?: string } {
	if (request.systemPrompt?.trim()) {
		return {
			agent: {
				name: request.agent?.trim() || "dynamic",
				description: request.description?.trim() || "Dynamically defined subagent",
				tools: request.tools && request.tools.length > 0 ? request.tools : undefined,
				model: request.model,
				systemPrompt: request.systemPrompt,

[800 more lines in file. Use offset=261 to continue.]


import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext } from "@mariozechner/pi-coding-agent";
import { appendToolUsageEvent } from "../_shared/tool-usage";

import type { SessionPickerResult } from "./picker.ts";
import { openSessionSwitchPicker } from "./picker.ts";
import { executeStartupAction, resolveStartupAction, resolveStartupSessionTarget } from "./relaunch.ts";

export function resolveCommandPickerAction(
	result: SessionPickerResult,
): { kind: "noop" | "shutdown" } | { kind: "switch"; sessionPath: string } {
	if (result.kind === "dismissed") {
		return result.reason === "exit" ? { kind: "shutdown" } : { kind: "noop" };
	}

	return { kind: "switch", sessionPath: result.sessionPath };
}

async function runSessionSwitchCommand(pi: ExtensionAPI, ctx: ExtensionCommandContext, commandName: string): Promise<void> {
	const startedAt = Date.now();
	appendToolUsageEvent({ kind: "extension_command", name: commandName, cwd: ctx.cwd, status: "started" });
	const result = await openSessionSwitchPicker(pi, ctx);
	const action = resolveCommandPickerAction(result);
	if (action.kind === "noop") {
		appendToolUsageEvent({ kind: "extension_command", name: commandName, cwd: ctx.cwd, status: "cancelled", durationMs: Date.now() - startedAt });
		return;
	}
	if (action.kind === "shutdown") {
		appendToolUsageEvent({ kind: "extension_command", name: commandName, cwd: ctx.cwd, status: "succeeded", durationMs: Date.now() - startedAt });
		ctx.shutdown();
		return;
	}
	if (action.kind !== "switch") {
		appendToolUsageEvent({ kind: "extension_command", name: commandName, cwd: ctx.cwd, status: "failed", durationMs: Date.now() - startedAt, errorSummary: "Unexpected picker action" });
		return;
	}

	const switchResult = await ctx.switchSession(action.sessionPath);
	if (switchResult.cancelled) {
		appendToolUsageEvent({ kind: "extension_command", name: commandName, cwd: ctx.cwd, status: "cancelled", durationMs: Date.now() - startedAt });
		ctx.ui.notify("Session switch cancelled", "info");
		return;
	}
	appendToolUsageEvent({ kind: "extension_command", name: commandName, cwd: ctx.cwd, status: "succeeded", durationMs: Date.now() - startedAt });
}

async function runSessionSwitchStartup(pi: ExtensionAPI, ctx: ExtensionContext): Promise<void> {
	const result = await openSessionSwitchPicker(pi, ctx);
	if (result.kind === "selected") {
		const target = resolveStartupSessionTarget(result.sessionPath);
		if ("warning" in target) {
			ctx.ui.notify(target.warning, "warning");
			return;
		}

		const action = resolveStartupAction(result, { cwd: target.cwd });
		executeStartupAction(ctx, action);
		return;
	}

	const action = resolveStartupAction(result, { cwd: ctx.cwd });
	executeStartupAction(ctx, action);
}

export default function sessionSwitchExtension(pi: ExtensionAPI) {
	pi.registerFlag("switch-session", {
		description: "Open the session-switch picker after startup, then relaunch into the selected session",
		type: "boolean",
		default: false,
	});

	pi.on("session_start", async (event, ctx) => {
		if (event.reason !== "startup" || !ctx.hasUI || pi.getFlag("switch-session") !== true) {
			return;
		}

		await runSessionSwitchStartup(pi, ctx);
	});

	const registerPickerCommand = (name: string, description: string) => {
		pi.registerCommand(name, {
			description,
			handler: async (_args, ctx) => {
				await runSessionSwitchCommand(pi, ctx, name);
			},
		});
	};

	registerPickerCommand("switch-session", "Session picker with live preview");
	registerPickerCommand("resum", "Session picker like /resume, sorted before it");
}


**Considering hook capabilities**

I need to explore the hook capabilities and whether it can return additional context. I'm uncertain if the extension can adjust the system prompt or provide examples. Using `ctx.ui.notify` for summaries and registering flags might not be sufficient. I could also consider creating AGENTS/hooks.

The user wants to clone the hook, so perhaps I can clone the session-start script into a standalone artifact for notifications. I might need to implement a reliable notification system and an environment script as well.

**Exploring extension capabilities**

I’m considering if the extension can read the hook and potentially inject its content. This might allow for some flexibility in how the hook operates within its environment. I need to think through the practicality of this and ensure it aligns with the user’s needs. It feels like an exciting possibility, and I wonder how far we can push this idea while maintaining functionality and reliability. Let’s see how this can work!

**Developing skill files**

I’m looking at the idea of creating Humand skill files under /.agents/skills. It seems I need a specific trigger to activate this. I'll create a skill called `humand-tech-context` that can load or read configurations and explain available commands. The user wants it to work automatically when in the folder, and an extension could potentially notify them. I think starting small might be sufficient. I’ll need to utilize some bash commands to write or copy content. Let's check the teams file; it might give me what I need.

set -e
mkdir -p /home/sebas/pi-config/humand/config/workflow /home/sebas/pi-config/humand/hooks
cp /home/sebas/work/humand/hu-ai-agent-plugin/plugins/humand-tech/config/workflow/repos.json /home/sebas/pi-config/humand/config/workflow/repos.json
cp /home/sebas/work/humand/hu-ai-agent-plugin/plugins/humand-tech/config/workflow/teams.json /home/sebas/pi-config/humand/config/workflow/teams.json
cp /home/sebas/work/humand/hu-ai-agent-plugin/plugins/humand-tech/config/workflow/local-config.example.json /home/sebas/pi-config/humand/config/workflow/local-config.example.json
cp /home/sebas/work/humand/hu-ai-agent-plugin/plugins/humand-tech/hooks/session-start /home/sebas/pi-config/humand/hooks/session-start
printf 'copied\n'


copied


**Creating setup scripts**

I need to create an adapted setup script and possibly an extension or skill. It’s on my mind that I should also inspect the teams file size, but I'm questioning if that's really necessary or if it's irrelevant. Maybe it would be a good idea to focus on writing first and then reevaluate the importance of file size later. Alright, let's dive into writing the script and see where it takes me!

write

#!/usr/bin/env bash
set -euo pipefail

script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
humand_root="$(cd "$script_dir" && pwd)"
workspace_root="${1:-$PWD}"
workspace_root="$(cd "$workspace_root" && pwd)"

config_home="${HUMAND_PI_CONFIG_HOME:-${XDG_CONFIG_HOME:-$HOME/.config}/pi-humand}"
state_home="${HUMAND_PI_STATE_HOME:-${XDG_STATE_HOME:-$HOME/.local/state}/pi-humand}"
bundled_config_dir="$humand_root/config/workflow"

if [ -f "$config_home/local-config.json" ]; then
  local_config_file="$config_home/local-config.json"
else
  local_config_file=""
fi

emit_var() {
  local key="$1"
  local value="$2"
  printf '%s=%q\n' "$key" "$value"
}

emit_var "HUMAND_PI_ROOT" "$humand_root"
emit_var "HUMAND_PI_WORKSPACE_ROOT" "$workspace_root"
emit_var "HUMAND_PI_CONFIG_HOME" "$config_home"
emit_var "HUMAND_PI_STATE_HOME" "$state_home"
emit_var "HUMAND_PI_REPOS_FILE" "$bundled_config_dir/repos.json"
emit_var "HUMAND_PI_TEAMS_FILE" "$bundled_config_dir/teams.json"
emit_var "HUMAND_PI_LOCAL_CONFIG_FILE" "$local_config_file"
emit_var "HUMAND_PI_LOCAL_CONFIG_EXAMPLE_FILE" "$bundled_config_dir/local-config.example.json"


Successfully wrote 1132 bytes to /home/sebas/pi-config/humand/workflow-setup.sh


import { execFileSync } from "node:child_process";
import path from "node:path";
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";

const HUMAND_ROOT = "/home/sebas/work/humand";
const HUMAND_ORGS = ["HumandDev", "sfavaron-hu"];
const NOTICE = [
	"Humand mode active.",
	"Isolated Humand workflow available.",
	"Source: pi-config/humand + hu-ai-agent-plugin clone.",
	"Use explicit Humand skills/commands only; non-Humand work stays unchanged.",
].join(" ");

function isInsideHumandTree(cwd: string): boolean {
	const resolved = path.resolve(cwd);
	return resolved === HUMAND_ROOT || resolved.startsWith(`${HUMAND_ROOT}/`);
}

function hasHumandRemote(cwd: string): boolean {
	try {
		const url = execFileSync("git", ["remote", "get-url", "origin"], {
			cwd,
			encoding: "utf8",
			stdio: ["ignore", "pipe", "ignore"],
		}).trim();
		return HUMAND_ORGS.some((org) => url.includes(org));
	} catch {
		return false;
	}
}

function detectHumandMode(cwd: string): boolean {
	return isInsideHumandTree(cwd) || hasHumandRemote(cwd);
}

export default function humandMode(pi: ExtensionAPI) {
	let announced = false;

	const maybeAnnounce = (ctx: any) => {
		if (announced) return;
		if (!ctx?.hasUI) return;
		if (!detectHumandMode(ctx.cwd)) return;
		announced = true;
		ctx.ui.notify(NOTICE, "info");
	};

	pi.on("session_start", async (_event, ctx) => {
		maybeAnnounce(ctx);
	});

	pi.registerCommand("humand-mode", {
		description: "Show whether isolated Humand mode is active for this session",
		handler: async (_args, ctx) => {
			const active = detectHumandMode(ctx.cwd);
			ctx.ui.notify(active ? NOTICE : "Humand mode inactive for current cwd.", active ? "info" : "warning");
		},
	});
}


Successfully wrote 1712 bytes to /home/sebas/pi-config/extensions/humand-mode.ts


**Exploring skill management**

I’m considering how skills might work in the /.agents directory. It seems available_skills are only read at the start of a session. For future use, I could create skill files directly in /.agents, and maybe source them in pi-config too. So, adding a simple skill file could be a good step forward. I’ll need to think through the details to make sure everything aligns correctly for smooth functionality!

---
name: humand-tech-context
description: Activate isolated Humand workflow context and load shared Humand multi-repo config
---

# humand-tech-context

Use only for Humand work.

## Goal

Load the isolated Humand workflow layer without affecting Ballbox/personal work.

## Files

- root: `/home/sebas/pi-config/humand`
- workflow setup: `/home/sebas/pi-config/humand/workflow-setup.sh`
- repos config: `/home/sebas/pi-config/humand/config/workflow/repos.json`
- teams config: `/home/sebas/pi-config/humand/config/workflow/teams.json`
- local config example: `/home/sebas/pi-config/humand/config/workflow/local-config.example.json`
- cloned upstream hook: `/home/sebas/pi-config/humand/hooks/session-start`
- source plugin clone: `/home/sebas/work/humand/hu-ai-agent-plugin/plugins/humand-tech`

## Workflow

1. Confirm task is Humand-specific.
2. Load setup vars:

```bash
eval "$(bash /home/sebas/pi-config/humand/workflow-setup.sh <workspace_root>)"
```

3. Use these env vars:
   - `HUMAND_PI_REPOS_FILE`
   - `HUMAND_PI_TEAMS_FILE`
   - `HUMAND_PI_LOCAL_CONFIG_FILE`
   - `HUMAND_PI_CONFIG_HOME`
   - `HUMAND_PI_STATE_HOME`
4. Prefer explicit Humand commands/skills. Do not change generic agent behavior globally.
5. If `HUMAND_PI_LOCAL_CONFIG_FILE` is empty, set it up before repo-resolution workflows.

## Current scope

Imported first:
- shared repo metadata
- teams metadata
- isolated workflow bootstrap
- cloned upstream session-start hook for reference
- session auto-detection notice via Pi extension

Not imported yet:
- full `/start`, `/status`, `/pr`, `/merge-watch`, `/summary`
- superpowers global enforcement
- marketplace/plugin manifests


Successfully wrote 1660 bytes to /home/sebas/.agents/skills/humand-tech-context/SKILL.md


**Considering installation steps**

I need to install an extension to the directory /.pi/agent/extensions. It seems I can just copy the file there. I'm wondering if I need to update any settings after that, but I don’t think there’s a load mechanism that scans the directory automatically. So, copying the file sounds like the right approach! I’ll go ahead and do that.

bash

set -e
cp /home/sebas/pi-config/extensions/humand-mode.ts /home/sebas/.pi/agent/extensions/humand-mode.ts
printf 'installed extension\n'


installed extension


cd /home/sebas/pi-config && npm run typecheck

bash /home/sebas/pi-config/humand/workflow-setup.sh /home/sebas/work/humand | sed -n '1,20p'

ls -la /home/sebas/.pi/agent/extensions/humand-mode.ts /home/sebas/.agents/skills/humand-tech-context/SKILL.md /home/sebas/pi-config/humand/hooks/session-start


> typecheck
> tsc --noEmit


-rw-r--r-- 1 sebas sebas 1660 Jun  9 10:52 /home/sebas/.agents/skills/humand-tech-context/SKILL.md
-rw-r--r-- 1 sebas sebas 1712 Jun  9 10:52 /home/sebas/.pi/agent/extensions/humand-mode.ts
-rwxr-xr-x 1 sebas sebas 2891 Jun  9 10:52 /home/sebas/pi-config/humand/hooks/session-start


**Considering workflow setup**

I think I need to make the workflow setup executable. This way, it can be run smoothly and efficiently. I'm not sure what specific steps I need to take, but I'm considering if I might need to check for dependencies or any specific configurations. I want to ensure it works correctly for users, so I'll take time to plan out the execution process. Let's explore the details together!

chmod +x /home/sebas/pi-config/humand/workflow-setup.sh /home/sebas/pi-config/humand/hooks/session-start && echo ok


ok


**Considering extension loading**

I’m thinking about whether I need an explicit test for loading the extension. It might require a new session to see any automatic notifications regarding that. Also, it seems that testing the command without restarting the Raspberry Pi might be impossible. I should make a note of that for the next steps. There’s a lot to think about as I figure out how to move forward effectively!


Updated #3

Clone all Humand repos locally

Clone every repository listed in /home/sebas/work/humand/REMOTE-URLS.tsv into the Humand workspace using the authenticated work GitHub account.

Analyze Humand agent functionality import into Pi agent

Inspect Humand laptop/repo setup, especially hu-ai-agent-plugin and humand-tech, then propose a minimal integration path into the Pi agent with soft isolation and partial real parity as the done gate for this stage.

implement isolated Humand mode with minimal workflow subset

Implement isolated Humand mode in Pi agent

Clone the Humand plugin behavior in parts, including the hook, but isolate it behind explicit/auto Humand activation with reliable session announcement and no effect outside Humand work.


Bien. Hice el primer corte.

## Traído y aislado

Copié a Pi:

- `repos.json`
- `teams.json`
- `local-config.example.json`
- hook `session-start` upstream

Destino:
- `/home/sebas/pi-config/humand/config/workflow/...`
- `/home/sebas/pi-config/humand/hooks/session-start`

También agregué bootstrap aislado:

- `/home/sebas/pi-config/humand/workflow-setup.sh`

Expone:
- `HUMAND_PI_REPOS_FILE`
- `HUMAND_PI_TEAMS_FILE`
- `HUMAND_PI_LOCAL_CONFIG_FILE`
- `HUMAND_PI_CONFIG_HOME`
- `HUMAND_PI_STATE_HOME`

## Trigger aislado

Agregué extensión Pi:

- `/home/sebas/.pi/agent/extensions/humand-mode.ts`

Comportamiento:
- auto-detecta Humand si:
  - cwd está bajo `/home/sebas/work/humand`
  - o `origin` apunta a `HumandDev` / `sfavaron-hu`
- anuncia una vez por sesión:
  - “Humand mode active...”
- no cambia nada fuera de Humand
- comando manual:
  - `/humand-mode`

## Skill explícito

Agregué:

- `/home/sebas/.agents/skills/humand-tech-context/SKILL.md`

Sirve para cargar el contexto Humand aislado de forma explícita.

## Validado

- `npm run typecheck` OK
- `workflow-setup.sh` OK

## Límite actual

Todavía **no** portée lógica real de:
- `/start`
- `/status`
- `/pr`
- `/merge-watch`
- `/summary`

Solo dejé:
- assets/config
- hook clonado
- bootstrap
- detección/aviso de sesión
- skill explícito

## Importante

Para ver el aviso automático, conviene **nueva sesión Pi** dentro de carpeta Humand.
La extensión ya quedó instalada, pero el auto-hook de sesión se nota mejor al arrancar de cero.

## Siguiente paso recomendado

Traer ahora, en este orden:

1. `humand-initial-setup`
2. `humand-status`
3. `humand-start` mínimo

Ese orden da valor real y prueba la aislación.

Si querés, sigo ya con:
**`humand-initial-setup` + `humand-status`**.


nueva sesion no lo autoactivo, pero el command anda (o loggea que anda), aunque no vi las variables cargadas

sebas@ballbox-first:~ $ cd work/humand/
sebas@ballbox-first:~/work/humand $ pi
Model scope: gpt-5.4, gpt-5.4-mini, nvidia/nemotron-3-super-120b-a12b:free, qwen/qwen3-coder:free (Ctrl+P to cycle)

 pi v0.78.1
 escape interrupt · ctrl+c/ctrl+d clear/exit · / commands · ! bash · ctrl+o more
 Press ctrl+o to show full startup help and loaded resources.

 Pi can explain its own features and look up its docs. Ask it how to use or extend Pi.

 Humand mode active. Isolated Humand workflow available. Source: pi-config/humand + hu-ai-agent-plugin clone. Use explicit Humand skills/commands only; non-Humand work stays
 unchanged.

[Context]
  ~/pi-config/AGENTS.md, ~/AGENTS.md

[Skills]
  ballbox-company, humand-tech-context, karaoke-stem-separation, memory-read, memory-write, pi-extension-search, telegram-notify, tmux-pi-job-run, tmux-pi-job-setup

[Extensions]
  @juicesharp/rpiv-todo, apmantza/pi-free, humand-mode.ts, pi-btw:btw.ts, pi-telegram-sebas, questionnaire.ts, resume-hint.js, session-memory-dispatcher.ts, session-switch,
subagent

───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
 Update Available
 New version 0.79.0 is available. Run pi update
 Changelog: https://pi.dev/changelog
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
 Package Updates Available
 Package updates are available. Run pi update
 Packages:
 - @juicesharp/rpiv-todo
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

 Humand mode active. Isolated Humand workflow available. Source: pi-config/humand + hu-ai-agent-plugin clone. Use explicit Humand skills/commands only; non-Humand work stays
 unchanged.

 quienes estan en el equipo shark?

 Searching for context

 I need to figure out the context for the user's question. It’s likely they’re referring to something specific, perhaps related to a shark team. Since the user is asking in
 Spanish, I need to keep that in mind as I look for answers. I’m considering searching through files in the current working directory to see if I can find more information.
 Let’s see what I can come up with!

 $ rg -n "team shark|shark team|equipo shark|Shark" /home/sebas/work/humand -S (timeout 10s)

 ... (3 earlier lines, ctrl+o to expand)
 /home/sebas/work/humand/hu-ai-agent-plugin/plugins/humand-product-workflow/scripts/generate-sprint-report.py:7:        --sprint "Shark 60" \\
 /home/sebas/work/humand/hu-ai-agent-plugin/plugins/humand-product-workflow/scripts/fetch-jira-sprint-issues.sh:10:#   ./fetch-jira-sprint-issues.sh --project SQSH --sprint
 "Shark 60"
 /home/sebas/work/humand/hu-ai-agent-plugin/plugins/humand-product-workflow/scripts/run-sprint-report.py:11:    python3 run-sprint-report.py --team SQSH --sprint "Shark 60"
 -o report.md

 Took 3.1s

 read ~/work/humand/hu-ai-agent-plugin/plugins/humand-tech/config/workflow/teams.json:640-669

 read ~/work/humand/hu-ai-agent-plugin/plugins/humand-tech/config/workflow/teams.json:670-749

 Equipo Shark (Squad Shark), según teams.json:

 - sebas (sfavaron-hu)
 - Juan Grethe (jcgrethe)
 - Fran (FranRoberti-hu)
 - Mario (maritocuate)
 - flopiano-hu
 - aatar-hu
 - Julián Ignacio Alvarez (jjuli93)

 Nota: esta lista sale de membresía inferida por PRs, no de una fuente oficial de HR.

 que variables de ambiente tenes cargadas?

 Considering environment variables

 I need to think about how to answer the user's question regarding what environment variables are loaded. While I could potentially list them all, it raises security concerns
 because some of them could contain sensitive information. It might be better to offer only the names of the variables and suggest a filtered dump instead. I could run a
 command like "env | cut" to get just the variable names without exposing any secrets.

 $ env | sed 's/=.*//' | sort (timeout 10s)

 ... (26 earlier lines, ctrl+o to expand)
 TEXTDOMAIN
 USER
 WARP_CLI_AGENT_PROTOCOL_VERSION
 WARP_CLIENT_VERSION
 WARP_IS_SSH

 Took 0.0s

 Tengo cargadas estas variables:

 - BRAVE_API_KEY
 - BUN_INSTALL
 - COLORTERM
 - DBUS_SESSION_BUS_ADDRESS
 - HOME
 - LANG
 - LC_ALL
 - LC_CTYPE
 - LS_COLORS
 - NO_AT_BRIDGE
 - OLDPWD
 - OPENROUTER_API_KEY
 - PATH
 - PERSONAL_AGENT_SHARED_MEMORY_DB_PATH
 - PI_CODING_AGENT
 - PI_CODING_AGENT_DIR
 - PLAYWRIGHT_BROWSERS_PATH
 - PWD
 - SHELL
 - SHLVL
 - SSH_CLIENT
 - SSH_CONNECTION
 - SSH_TTY
 - TERM
 - TERM_PROGRAM
 - TEXTDOMAIN
 - USER
 - WARP_CLI_AGENT_PROTOCOL_VERSION
 - WARP_CLIENT_VERSION
 - WARP_IS_SSH

 No voy a volcar valores sensibles como *_KEY.

 Si querés:
 - te digo el valor de una variable puntual
 - o te doy solo las no sensibles con valor

───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────


todo

auto-announce duplicates; Humand vars not injected into session env/context

add dedupe + startup context command/notice with resolved vars


Updated #3

Clone all Humand repos locally


bash

cd /home/sebas/pi-config && grep -RIn "additionalContext\|additional_context\|registerCommand(\".*env\|session_start" extensions . | sed -n '1,220p'

node -e "const p=require('@mariozechner/pi-coding-agent'); console.log(Object.keys(p).sort().join('\n'))" 2>/dev/null | sed -n '1,200p'


./git/github.com/apmantza/pi-free/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/types.d.ts:369:/** Fired after session_start to allow extensions to provide additional resource paths. */
./git/github.com/apmantza/pi-free/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/types.d.ts:383:    type: "session_start";
./git/github.com/apmantza/pi-free/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/types.d.ts:785:    on(event: "session_start", handler: ExtensionHandler<SessionStartEvent>): void;
./git/github.com/apmantza/pi-free/node_modules/@earendil-works/pi-coding-agent/docs/custom-provider.md:62:The extension factory can also be `async`. For dynamic model discovery, fetch and register models in the factory instead of `session_start`. pi waits for the factory before startup continues, so the provider is available during interactive startup and to `pi --list-models`.
./git/github.com/apmantza/pi-free/node_modules/@earendil-works/pi-coding-agent/docs/tui.md:92:pi.on("session_start", async (_event, ctx) => {
./git/github.com/apmantza/pi-free/node_modules/@earendil-works/pi-coding-agent/docs/tui.md:878:  pi.on("session_start", (_event, ctx) => {
./git/github.com/apmantza/pi-free/node_modules/@earendil-works/pi-coding-agent/docs/extensions.md:65:  pi.on("session_start", async (_event, ctx) => {
./git/github.com/apmantza/pi-free/node_modules/@earendil-works/pi-coding-agent/docs/extensions.md:180:If the factory returns a `Promise`, pi awaits it before continuing startup. That means async initialization completes before `session_start`, before `resources_discover`, and before provider registrations queued via `pi.registerProvider()` are flushed.

[Showing lines 213-220 of 220 (50.0KB limit). Full output: /tmp/pi-bash-fa46a81b464ab6c3.log]


(no output)


     * If omitted, the default execution mode applies.
     */
    executionMode?: ToolExecutionMode;
    /** Execute the tool. */
    execute(toolCallId: string, params: Static<TParams>, signal: AbortSignal | undefined, onUpdate: AgentToolUpdateCallback<TDetails> | undefined, ctx: ExtensionContext): Promise<AgentToolResult<TDetails>>;
    /** Custom rendering for tool call display */
    renderCall?: (args: Static<TParams>, theme: Theme, context: ToolRenderContext<TState, Static<TParams>>) => Component;
    /** Custom rendering for tool result display */
    renderResult?: (result: AgentToolResult<TDetails>, options: ToolRenderResultOptions, theme: Theme, context: ToolRenderContext<TState, Static<TParams>>) => Component;
}
type AnyToolDefinition = ToolDefinition<any, any, any>;
/**
 * Preserve parameter inference for standalone tool definitions.
 *
 * Use this when assigning a tool to a variable or passing it through arrays such
 * as `customTools`, where contextual typing would otherwise widen params to
 * `unknown`.
 */
export declare function defineTool<TParams extends TSchema, TDetails = unknown, TState = any>(tool: ToolDefinition<TParams, TDetails, TState>): ToolDefinition<TParams, TDetails, TState> & AnyToolDefinition;
/** Fired after session_start to allow extensions to provide additional resource paths. */
export interface ResourcesDiscoverEvent {
    type: "resources_discover";
    cwd: string;
    reason: "startup" | "reload";
}
/** Result from resources_discover event handler */
export interface ResourcesDiscoverResult {
    skillPaths?: string[];
    promptPaths?: string[];
    themePaths?: string[];
}
/** Fired when a session is started, loaded, or reloaded */
export interface SessionStartEvent {
    type: "session_start";
    /** Why this session start happened. */
    reason: "startup" | "reload" | "new" | "resume" | "fork";
    /** Previously active session file. Present for "new", "resume", and "fork". */
    previousSessionFile?: string;
}
/** Fired before switching to another session (can be cancelled) */
export interface SessionBeforeSwitchEvent {
    type: "session_before_switch";
    reason: "new" | "resume";
    targetSessionFile?: string;
}
/** Fired before forking a session (can be cancelled) */
export interface SessionBeforeForkEvent {
    type: "session_before_fork";
    entryId: string;
    position: "before" | "at";
}
/** Fired before context compaction (can be cancelled or customized) */
export interface SessionBeforeCompactEvent {
    type: "session_before_compact";
    preparation: CompactionPreparation;
    branchEntries: SessionEntry[];
    customInstructions?: string;
    signal: AbortSignal;
}
/** Fired after context compaction */
export interface SessionCompactEvent {
    type: "session_compact";
    compactionEntry: CompactionEntry;
    fromExtension: boolean;
}
/** Fired before an extension runtime is torn down due to quit, reload, or session replacement. */
export interface SessionShutdownEvent {
    type: "session_shutdown";
    reason: "quit" | "reload" | "new" | "resume" | "fork";
    /** Destination session file when shutting down due to session replacement. */
    targetSessionFile?: string;
}
/** Preparation data for tree navigation */
export interface TreePreparation {
    targetId: string;
    oldLeafId: string | null;
    commonAncestorId: string | null;
    entriesToSummarize: SessionEntry[];
    userWantsSummary: boolean;
    /** Custom instructions for summarization */
    customInstructions?: string;
    /** If true, customInstructions replaces the default prompt instead of being appended */
    replaceInstructions?: boolean;
    /** Label to attach to the branch summary entry */
    label?: string;
}
/** Fired before navigating in the session tree (can be cancelled) */
export interface SessionBeforeTreeEvent {
    type: "session_before_tree";
    preparation: TreePreparation;
    signal: AbortSignal;
}
/** Fired after navigating in the session tree */
export interface SessionTreeEvent {
    type: "session_tree";
    newLeafId: string | null;
    oldLeafId: string | null;
    summaryEntry?: BranchSummaryEntry;
    fromExtension?: boolean;
}
export type SessionEvent = SessionStartEvent | SessionBeforeSwitchEvent | SessionBeforeForkEvent | SessionBeforeCompactEvent | SessionCompactEvent | SessionShutdownEvent | SessionBeforeTreeEvent | SessionTreeEvent;
/** Fired before each LLM call. Can modify messages. */
export interface ContextEvent {
    type: "context";
    messages: AgentMessage[];
}
/** Fired before a provider request is sent. Can replace the payload. */
export interface BeforeProviderRequestEvent {
    type: "before_provider_request";
    payload: unknown;
}
/** Fired after a provider response is received and before the response stream is consumed. */
export interface AfterProviderResponseEvent {
    type: "after_provider_response";
    status: number;
    headers: Record<string, string>;
}
/** Fired after user submits prompt but before agent loop. */
export interface BeforeAgentStartEvent {
    type: "before_agent_start";

[704 more lines in file. Use offset=470 to continue.]


**Evaluating implementation details**

I'm thinking about inspecting the documentation to see if promptPaths loads prompt resources. I guess it’s enough to implement the prompt file with variables and instructions. I wonder if it’s worth mentioning the unresolved local configuration since the variables aren't loaded into the shell's environment, but the context is still visible. I'll explore this further to clarify things and ensure everything is set up properly. Good thinking!

write

# Humand Mode

This session is in Humand mode.

Scope:
- apply Humand workflow only for Humand work
- keep Ballbox/personal/general Pi behavior unchanged
- prefer explicit Humand skills/commands for Humand workflows

Bootstrap:
- setup script: `/home/sebas/pi-config/humand/workflow-setup.sh`
- repos config: `/home/sebas/pi-config/humand/config/workflow/repos.json`
- teams config: `/home/sebas/pi-config/humand/config/workflow/teams.json`
- local config home: `~/.config/pi-humand`
- state home: `~/.local/state/pi-humand`
- upstream cloned hook reference: `/home/sebas/pi-config/humand/hooks/session-start`

Rules:
- do not apply Humand workflow outside Humand context
- if Humand repo/worktree resolution is needed, first run:
  `eval "$(bash /home/sebas/pi-config/humand/workflow-setup.sh <workspace_root>)"`
- use `HUMAND_PI_*` variables from that bootstrap output
- if `HUMAND_PI_LOCAL_CONFIG_FILE` is empty, Humand repo-local path setup is incomplete


Successfully wrote 959 bytes to /home/sebas/pi-config/humand/HUMAND.md


import { execFileSync } from "node:child_process";
import path from "node:path";
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";

const HUMAND_ROOT = "/home/sebas/work/humand";
const HUMAND_PROMPT = "/home/sebas/pi-config/humand/HUMAND.md";
const HUMAND_ORGS = ["HumandDev", "sfavaron-hu"];
const NOTICE = [
	"Humand mode active.",
	"Isolated Humand workflow available.",
	"Source: pi-config/humand + hu-ai-agent-plugin clone.",
	"Use explicit Humand skills/commands only; non-Humand work stays unchanged.",
].join(" ");

function isInsideHumandTree(cwd: string): boolean {
	const resolved = path.resolve(cwd);
	return resolved === HUMAND_ROOT || resolved.startsWith(`${HUMAND_ROOT}/`);
}

function hasHumandRemote(cwd: string): boolean {
	try {
		const url = execFileSync("git", ["remote", "get-url", "origin"], {
			cwd,
			encoding: "utf8",
			stdio: ["ignore", "pipe", "ignore"],
		}).trim();
		return HUMAND_ORGS.some((org) => url.includes(org));
	} catch {
		return false;
	}
}

function detectHumandMode(cwd: string): boolean {
	return isInsideHumandTree(cwd) || hasHumandRemote(cwd);
}

function describeHumandEnv(cwd: string): string[] {
	const inTree = isInsideHumandTree(cwd) ? "yes" : "no";
	const remote = hasHumandRemote(cwd) ? "yes" : "no";
	return [
		`cwd=${cwd}`,
		`humand_tree=${inTree}`,
		`humand_remote=${remote}`,
		`humand_prompt=${HUMAND_PROMPT}`,
		"bootstrap=bash /home/sebas/pi-config/humand/workflow-setup.sh <workspace_root>",
	];
}

export default function humandMode(pi: ExtensionAPI) {
	let lastAnnouncedKey: string | null = null;

	const maybeAnnounce = (ctx: any, reason?: string) => {
		if (!ctx?.hasUI) return;
		if (!detectHumandMode(ctx.cwd)) return;
		const key = `${ctx.cwd}:${reason || ""}`;
		if (lastAnnouncedKey === key) return;
		lastAnnouncedKey = key;
		ctx.ui.notify(`${NOTICE} ${describeHumandEnv(ctx.cwd).join(" | ")}`, "info");
	};

	pi.on("resources_discover", async (event) => {
		if (!detectHumandMode(event.cwd)) return undefined;
		return { promptPaths: [HUMAND_PROMPT] };
	});

	pi.on("session_start", async (event, ctx) => {
		if (event.reason === "reload") return;
		maybeAnnounce(ctx, event.reason);
	});

	pi.registerCommand("humand-mode", {
		description: "Show whether isolated Humand mode is active for this session",
		handler: async (_args, ctx) => {
			const active = detectHumandMode(ctx.cwd);
			const text = active
				? `${NOTICE} ${describeHumandEnv(ctx.cwd).join(" | ")}`
				: `Humand mode inactive for current cwd. cwd=${ctx.cwd}`;
			ctx.ui.notify(text, active ? "info" : "warning");
		},
	});
}


Successfully wrote 2593 bytes to /home/sebas/pi-config/extensions/humand-mode.ts


bash

set -e
cp /home/sebas/pi-config/extensions/humand-mode.ts /home/sebas/.pi/agent/extensions/humand-mode.ts
cd /home/sebas/pi-config && npm run typecheck


> typecheck
> tsc --noEmit


todo

added env/bootstrap details to humand-mode notice


Sí. Bien visto.

Pasaba esto:

1. **autoactivó solo el aviso**, no las vars
2. el aviso salió **duplicado**
3. faltaba **contexto de sesión cargado** de forma visible/reliable

Ya lo ajusté.

## Qué cambié

### 1. Prompt/contexto Humand cargado en sesión
Agregué:

- `/home/sebas/pi-config/humand/HUMAND.md`

Y la extensión ahora lo inyecta como **prompt resource** cuando estás en contexto Humand.

O sea: no “exporta vars al shell global de Pi”, pero **sí carga contexto Humand en la sesión**.

### 2. Aviso con más info útil
`/humand-mode` y el aviso startup ahora muestran además:

- `cwd=...`
- `humand_tree=yes/no`
- `humand_remote=yes/no`
- `humand_prompt=/home/sebas/pi-config/humand/HUMAND.md`
- bootstrap command

### 3. Menos duplicado
Hice dedupe básico y además ignora `reload`.

## Importante

Las `HUMAND_PI_*` **no aparecen automáticamente en `env`** de la shell del agente.
Eso hoy es intencional.

Se obtienen con:

```bash
eval "$(bash /home/sebas/pi-config/humand/workflow-setup.sh <workspace_root>)"
```

Porque las estamos manteniendo **aisladas por workflow**, no globales.

## Qué deberías ver ahora en nueva sesión

En `~/work/humand`:

- aviso Humand una vez
- `[Context]` debería incluir también el prompt Humand
- `/humand-mode` debería mostrar los detalles

## Próximo corte
Si querés sigo con esto:

- `humand-initial-setup`
- `humand-status`

Eso ya empieza a usar las `HUMAND_PI_*` de verdad.


sacame del testing loop lo mas que puedas, levanta tus propios agentes pi. intenta conseguir pasar todo de a poco y que ande lo mas reliable posible


**Planning execution steps**

It sounds like the user wants me to comply with using subagents, potentially creating a to-do list. I think I should clarify the exact request since they want a full execution. Maybe I'll look into using Ralph? I’ll need to have a plan and consider starting the implementation. I also want to inspect the extension API docs for additional resources. Using a subagent for research and another for implementation could be a good approach, but I can handle some of these tasks myself. I'll update my to-do list accordingly.

self-validate each slice with local pi sessions/subagents and only hand off when reliable


Self-test Humand mode session loading

Run Pi sessions locally in Humand and non-Humand directories to verify prompt/context loading, notice behavior, and command behavior without user testing.


Created #4: Self-test Humand mode session loading (pending)

Self-test Humand mode session loading

Clone all Humand repos locally

Self-test Humand mode session loading


Implement Humand setup/status commands

Add isolated Humand commands/skills that resolve workflow bootstrap and report effective config so Humand mode becomes operational and self-verifiable.


Created #5: Implement Humand setup/status commands (pending)

Implement Humand setup/status commands

Clone all Humand repos locally

Self-test Humand mode session loading

Implement Humand setup/status commands


Updated #4 (pending → in_progress)

Clone all Humand repos locally

Self-test Humand mode session loading

Implement Humand setup/status commands


bash

cd /home/sebas/pi-config && pi --help | sed -n '1,220p'

cd /home/sebas/pi-config && rg -n "--prompt-file|-p |json mode|--json|--print|--no-extensions" /home/sebas/pi-config/git/github.com/apmantza/pi-free/node_modules/@earendil-works/pi-coding-agent/docs -S | sed -n '1,220p'


> pi can create extensions. Ask it to build one for your use case.

# Extensions

Extensions are TypeScript modules that extend pi's behavior. They can subscribe to lifecycle events, register custom tools callable by the LLM, add commands, and more.

> **Placement for /reload:** Put extensions in `~/.pi/agent/extensions/` (global) or `.pi/extensions/` (project-local) for auto-discovery. Use `pi -e ./path.ts` only for quick tests. Extensions in auto-discovered locations can be hot-reloaded with `/reload`.

**Key capabilities:**
- **Custom tools** - Register tools the LLM can call via `pi.registerTool()`
- **Event interception** - Block or modify tool calls, inject context, customize compaction
- **User interaction** - Prompt users via `ctx.ui` (select, confirm, input, notify)
- **Custom UI components** - Full TUI components with keyboard input via `ctx.ui.custom()` for complex interactions
- **Custom commands** - Register commands like `/mycommand` via `pi.registerCommand()`
- **Session persistence** - Store state that survives restarts via `pi.appendEntry()`
- **Custom rendering** - Control how tool calls/results and messages appear in TUI

**Example use cases:**
- Permission gates (confirm before `rm -rf`, `sudo`, etc.)
- Git checkpointing (stash at each turn, restore on branch)
- Path protection (block writes to `.env`, `node_modules/`)
- Custom compaction (summarize conversation your way)
- Conversation summaries (see `summarize.ts` example)
- Interactive tools (questions, wizards, custom dialogs)
- Stateful tools (todo lists, connection pools)
- External integrations (file watchers, webhooks, CI triggers)
- Games while you wait (see `snake.ts` example)

See [examples/extensions/](../examples/extensions/) for working implementations.

## Table of Contents

- [Quick Start](#quick-start)
- [Extension Locations](#extension-locations)
- [Available Imports](#available-imports)
- [Writing an Extension](#writing-an-extension)
  - [Extension Styles](#extension-styles)
- [Events](#events)
  - [Lifecycle Overview](#lifecycle-overview)
  - [Resource Events](#resource-events)
  - [Session Events](#session-events)
  - [Agent Events](#agent-events)
  - [Model Events](#model-events)
  - [Tool Events](#tool-events)
- [ExtensionContext](#extensioncontext)
- [ExtensionCommandContext](#extensioncommandcontext)
- [ExtensionAPI Methods](#extensionapi-methods)
- [State Management](#state-management)
- [Custom Tools](#custom-tools)
- [Custom UI](#custom-ui)
- [Error Handling](#error-handling)
- [Mode Behavior](#mode-behavior)
- [Examples Reference](#examples-reference)

## Quick Start

Create `~/.pi/agent/extensions/my-extension.ts`:

```typescript
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
import { Type } from "typebox";

export default function (pi: ExtensionAPI) {
  // React to events
  pi.on("session_start", async (_event, ctx) => {
    ctx.ui.notify("Extension loaded!", "info");
  });

  pi.on("tool_call", async (event, ctx) => {
    if (event.toolName === "bash" && event.input.command?.includes("rm -rf")) {
      const ok = await ctx.ui.confirm("Dangerous!", "Allow rm -rf?");
      if (!ok) return { block: true, reason: "Blocked by user" };
    }
  });

  // Register a custom tool
  pi.registerTool({
    name: "greet",
    label: "Greet",
    description: "Greet someone by name",
    parameters: Type.Object({
      name: Type.String({ description: "Name to greet" }),
    }),
    async execute(toolCallId, params, signal, onUpdate, ctx) {
      return {
        content: [{ type: "text", text: `Hello, ${params.name}!` }],
        details: {},
      };
    },
  });

  // Register a command
  pi.registerCommand("hello", {
    description: "Say hello",
    handler: async (args, ctx) => {
      ctx.ui.notify(`Hello ${args || "world"}!`, "info");
    },
  });
}
```

Test with `--extension` (or `-e`) flag:

```bash
pi -e ./my-extension.ts
```

## Extension Locations

> **Security:** Extensions run with your full system permissions and can execute arbitrary code. Only install from sources you trust.

Extensions are auto-discovered from:

| Location | Scope |
|----------|-------|
| `~/.pi/agent/extensions/*.ts` | Global (all projects) |
| `~/.pi/agent/extensions/*/index.ts` | Global (subdirectory) |
| `.pi/extensions/*.ts` | Project-local |
| `.pi/extensions/*/index.ts` | Project-local (subdirectory) |

Additional paths via `settings.json`:

```json
{
  "packages": [
    "npm:@foo/bar@1.0.0",
    "git:github.com/user/repo@v1"
  ],
  "extensions": [
    "/path/to/local/extension.ts",
    "/path/to/local/extension/dir"
  ]
}
```

To share extensions via npm or git as pi packages, see [packages.md](packages.md).

## Available Imports

| Package | Purpose |
|---------|---------|
| `@earendil-works/pi-coding-agent` | Extension types (`ExtensionAPI`, `ExtensionContext`, events) |
| `typebox` | Schema definitions for tool parameters |
| `@earendil-works/pi-ai` | AI utilities (`StringEnum` for Google-compatible enums) |
| `@earendil-works/pi-tui` | TUI components for custom rendering |

npm dependencies work too. Add a `package.json` next to your extension (or in a parent directory), run `npm install`, and imports from `node_modules/` are resolved automatically.

For distributed pi packages installed with `pi install` (npm or git), runtime deps must be in `dependencies`. Package installation uses production installs (`npm install --omit=dev`) by default, so `devDependencies` are not available at runtime; when `npmCommand` is configured, git packages use plain `install` for compatibility with wrappers.

Node.js built-ins (`node:fs`, `node:path`, etc.) are also available.

## Writing an Extension

An extension exports a default factory function that receives `ExtensionAPI`. The factory can be synchronous or asynchronous:

```typescript
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";

export default function (pi: ExtensionAPI) {
  // Subscribe to events
  pi.on("event_name", async (event, ctx) => {
    // ctx.ui for user interaction
    const ok = await ctx.ui.confirm("Title", "Are you sure?");
    ctx.ui.notify("Done!", "success");
    ctx.ui.setStatus("my-ext", "Processing...");  // Footer status
    ctx.ui.setWidget("my-ext", ["Line 1", "Line 2"]);  // Widget above editor (default)
  });

  // Register tools, commands, shortcuts, flags
  pi.registerTool({ ... });
  pi.registerCommand("name", { ... });
  pi.registerShortcut("ctrl+x", { ... });
  pi.registerFlag("my-flag", { ... });
}
```

Extensions are loaded via [jiti](https://github.com/unjs/jiti), so TypeScript works without compilation.

If the factory returns a `Promise`, pi awaits it before continuing startup. That means async initialization completes before `session_start`, before `resources_discover`, and before provider registrations queued via `pi.registerProvider()` are flushed.

### Async factory functions

Use an async factory for one-time startup work such as fetching remote configuration or dynamically discovering available models.

```typescript
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";

export default async function (pi: ExtensionAPI) {
  const response = await fetch("http://localhost:1234/v1/models");
  const payload = (await response.json()) as {
    data: Array<{
      id: string;
      name?: string;
      context_window?: number;
      max_tokens?: number;
    }>;
  };

  pi.registerProvider("local-openai", {
    baseUrl: "http://localhost:1234/v1",
    apiKey: "LOCAL_OPENAI_API_KEY",
    api: "openai-completions",
    models: payload.data.map((model) => ({
      id: model.id,
      name: model.name ?? model.id,
      reasoning: false,
      input: ["text"],
      cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
      contextWindow: model.context_window ?? 128000,
      maxTokens: model.max_tokens ?? 4096,
    })),
  });
}
```

This pattern makes the fetched models available during normal startup and to `pi --list-models`.

### Extension Styles

**Single file** - simplest, for small extensions:

```
~/.pi/agent/extensions/
└── my-extension.ts
```

**Directory with index.ts** - for multi-file extensions:

```
~/.pi/agent/extensions/
└── my-extension/
    ├── index.ts        # Entry point (exports default function)
    ├── tools.ts        # Helper module
    └── utils.ts        # Helper module
```

**Package with dependencies** - for extensions that need npm packages:

```
~/.pi/agent/extensions/
└── my-extension/
    ├── package.json    # Declares dependencies and entry points
    ├── package-lock.json
    ├── node_modules/   # After npm install
    └── src/
        └── index.ts
```

```json
// package.json
{
  "name": "my-extension",
  "dependencies": {
    "zod": "^3.0.0",
    "chalk": "^5.0.0"
  },
  "pi": {
    "extensions": ["./src/index.ts"]
  }

[2337 more lines in file. Use offset=261 to continue.]


pi - AI coding assistant with read, bash, edit, write tools

Usage:
  pi [options] [@files...] [messages...]

Commands:
  pi install <source> [-l]     Install extension source and add to settings
  pi remove <source> [-l]      Remove extension source from settings
  pi uninstall <source> [-l]   Alias for remove
  pi update [source|self|pi]   Update pi and installed extensions
  pi list                      List installed extensions from settings
  pi config                    Open TUI to enable/disable package resources
  pi <command> --help          Show help for install/remove/uninstall/update/list

Options:
  --provider <name>              Provider name (default: google)
  --model <pattern>              Model pattern or ID (supports "provider/id" and optional ":<thinking>")
  --api-key <key>                API key (defaults to env vars)
  --system-prompt <text>         System prompt (default: coding assistant prompt)
  --append-system-prompt <text>  Append text or file contents to the system prompt (can be used multiple times)
  --mode <mode>                  Output mode: text (default), json, or rpc
  --print, -p                    Non-interactive mode: process prompt and exit
  --continue, -c                 Continue previous session
  --resume, -r                   Select a session to resume
  --session <path|id>            Use specific session file or partial UUID
  --session-id <id>              Use exact project session ID, creating it if missing
  --fork <path|id>               Fork specific session file or partial UUID into a new session
  --session-dir <dir>            Directory for session storage and lookup
  --no-session                   Don't save session (ephemeral)
  --name, -n <name>              Set session display name
  --models <patterns>            Comma-separated model patterns for Ctrl+P cycling
                                 Supports globs (anthropic/*, *sonnet*) and fuzzy matching
  --no-tools, -nt                Disable all tools by default (built-in and extension)
  --no-builtin-tools, -nbt       Disable built-in tools by default but keep extension/custom tools enabled
  --tools, -t <tools>            Comma-separated allowlist of tool names to enable
                                 Applies to built-in, extension, and custom tools
  --exclude-tools, -xt <tools>   Comma-separated denylist of tool names to disable
                                 Applies to built-in, extension, and custom tools
  --thinking <level>             Set thinking level: off, minimal, low, medium, high, xhigh
  --extension, -e <path>         Load an extension file (can be used multiple times)
  --no-extensions, -ne           Disable extension discovery (explicit -e paths still work)
  --skill <path>                 Load a skill file or directory (can be used multiple times)
  --no-skills, -ns               Disable skills discovery and loading
  --prompt-template <path>       Load a prompt template file or directory (can be used multiple times)
  --no-prompt-templates, -np     Disable prompt template discovery and loading
  --theme <path>                 Load a theme file or directory (can be used multiple times)
  --no-themes                    Disable theme discovery and loading
  --no-context-files, -nc        Disable AGENTS.md and CLAUDE.md discovery and loading
  --export <file>                Export session file to HTML and exit
  --list-models [search]         List available models (with optional fuzzy search)
  --verbose                      Force verbose startup (overrides quietStartup setting)
  --offline                      Disable startup network operations (same as PI_OFFLINE=1)
  --help, -h                     Show this help
  --version, -v                  Show version number

Extensions can register additional flags (e.g., --plan from plan-mode extension).
Extension CLI Flags:
  --switch-session            Open the session-switch picker after startup, then relaunch into the selected session

Examples:
  # Interactive mode
  pi

  # Interactive mode with initial prompt
  pi "List all .ts files in src/"

  # Include files in initial message
  pi @prompt.md @image.png "What color is the sky?"

  # Non-interactive mode (process and exit)
  pi -p "List all .ts files in src/"

  # Multiple messages (interactive)
  pi "Read package.json" "What dependencies do we have?"

  # Continue previous session
  pi --continue "What did we discuss?"

  # Start a named session
  pi --name "Refactor auth module"

  # Use different model
  pi --provider openai --model gpt-4o-mini "Help me refactor this code"

  # Use model with provider prefix (no --provider needed)
  pi --model openai/gpt-4o "Help me refactor this code"

  # Use model with thinking level shorthand
  pi --model sonnet:high "Solve this complex problem"

  # Limit model cycling to specific models
  pi --models claude-sonnet,claude-haiku,gpt-4o

  # Limit to a specific provider with glob pattern
  pi --models "github-copilot/*"

  # Cycle models with fixed thinking levels
  pi --models sonnet:high,haiku:low

  # Start with a specific thinking level
  pi --thinking high "Solve this complex problem"

  # Read-only mode (no file modifications possible)
  pi --tools read,grep,find,ls -p "Review the code in src/"

  # Disable one tool while keeping the rest available
  pi --exclude-tools ask_question

  # Export a session file to HTML
  pi --export ~/.pi/agent/sessions/--path--/session.jsonl
  pi --export session.jsonl output.html

Environment Variables:
  ANTHROPIC_API_KEY                - Anthropic Claude API key
  ANTHROPIC_OAUTH_TOKEN            - Anthropic OAuth token (alternative to API key)
  ANT_LING_API_KEY                 - Ant Ling API key
  OPENAI_API_KEY                   - OpenAI GPT API key
  AZURE_OPENAI_API_KEY             - Azure OpenAI API key
  AZURE_OPENAI_BASE_URL            - Azure OpenAI/Cognitive Services base URL (e.g. https://{resource}.openai.azure.com)
  AZURE_OPENAI_RESOURCE_NAME       - Azure OpenAI resource name (alternative to base URL)
  AZURE_OPENAI_API_VERSION         - Azure OpenAI API version (default: v1)
  AZURE_OPENAI_DEPLOYMENT_NAME_MAP - Azure OpenAI model=deployment map (comma-separated)
  DEEPSEEK_API_KEY                 - DeepSeek API key
  NVIDIA_API_KEY                   - NVIDIA NIM API key
  GEMINI_API_KEY                   - Google Gemini API key
  GROQ_API_KEY                     - Groq API key
  CEREBRAS_API_KEY                 - Cerebras API key
  XAI_API_KEY                      - xAI Grok API key
  FIREWORKS_API_KEY                - Fireworks API key
  TOGETHER_API_KEY                 - Together AI API key
  OPENROUTER_API_KEY               - OpenRouter API key
  AI_GATEWAY_API_KEY               - Vercel AI Gateway API key
  ZAI_API_KEY                      - ZAI API key
  ZAI_CODING_CN_API_KEY            - ZAI Coding Plan API key (China)
  MISTRAL_API_KEY                  - Mistral API key
  MINIMAX_API_KEY                  - MiniMax API key
  MOONSHOT_API_KEY                 - Moonshot AI API key
  OPENCODE_API_KEY                 - OpenCode Zen/OpenCode Go API key
  KIMI_API_KEY                     - Kimi For Coding API key
  CLOUDFLARE_API_KEY               - Cloudflare API token (Workers AI and AI Gateway)
  CLOUDFLARE_ACCOUNT_ID            - Cloudflare account id (required for both)
  CLOUDFLARE_GATEWAY_ID            - Cloudflare AI Gateway slug (required for AI Gateway)
  XIAOMI_API_KEY                   - Xiaomi MiMo API key (api.xiaomimimo.com billing)
  XIAOMI_TOKEN_PLAN_CN_API_KEY     - Xiaomi MiMo Token Plan API key (China region)
  XIAOMI_TOKEN_PLAN_AMS_API_KEY    - Xiaomi MiMo Token Plan API key (Amsterdam region)
  XIAOMI_TOKEN_PLAN_SGP_API_KEY    - Xiaomi MiMo Token Plan API key (Singapore region)
  AWS_PROFILE                      - AWS profile for Amazon Bedrock
  AWS_ACCESS_KEY_ID                - AWS access key for Amazon Bedrock
  AWS_SECRET_ACCESS_KEY            - AWS secret key for Amazon Bedrock
  AWS_BEARER_TOKEN_BEDROCK         - Bedrock API key (bearer token)
  AWS_REGION                       - AWS region for Amazon Bedrock (e.g., us-east-1)
  PI_CODING_AGENT_DIR              - Config directory (default: ~/.pi/agent)
  PI_CODING_AGENT_SESSION_DIR      - Session storage directory (overridden by --session-dir)
  PI_PACKAGE_DIR                   - Override package directory (for Nix/Guix store paths)
  PI_OFFLINE                       - Disable startup network operations when set to 1/true/yes
  PI_TELEMETRY                     - Override install telemetry when set to 1/true/yes or 0/false/no
  PI_SHARE_VIEWER_URL              - Base URL for /share command (default: https://pi.dev/session/)

Built-in Tool Names:
  read   - Read file contents
  bash   - Execute bash commands
  edit   - Edit files with find/replace
  write  - Write files (creates/overwrites)
  grep   - Search file contents (read-only, off by default)
  find   - Find files by glob pattern (read-only, off by default)
  ls     - List directory contents (read-only, off by default)


rg: unrecognized flag --prompt-file|-p |json mode|--json|--print|--no-extensions


/**
 * Action implementations for pi.* API methods.
 * Provided to runner.initialize(), copied into the shared runtime.
 */
export interface ExtensionActions {
    sendMessage: SendMessageHandler;
    sendUserMessage: SendUserMessageHandler;
    appendEntry: AppendEntryHandler;
    setSessionName: SetSessionNameHandler;
    getSessionName: GetSessionNameHandler;
    setLabel: SetLabelHandler;
    getActiveTools: GetActiveToolsHandler;
    getAllTools: GetAllToolsHandler;
    setActiveTools: SetActiveToolsHandler;
    refreshTools: RefreshToolsHandler;
    getCommands: GetCommandsHandler;
    setModel: SetModelHandler;
    getThinkingLevel: GetThinkingLevelHandler;
    setThinkingLevel: SetThinkingLevelHandler;
}
/**
 * Actions for ExtensionContext (ctx.* in event handlers).
 * Required by all modes.
 */
export interface ExtensionContextActions {
    getModel: () => Model<any> | undefined;
    isIdle: () => boolean;
    getSignal: () => AbortSignal | undefined;
    abort: () => void;
    hasPendingMessages: () => boolean;
    shutdown: () => void;
    getContextUsage: () => ContextUsage | undefined;
    compact: (options?: CompactOptions) => void;
    getSystemPrompt: () => string;
}
/**
 * Actions for ExtensionCommandContext (ctx.* in command handlers).
 * Only needed for interactive mode where extension commands are invokable.
 */
export interface ExtensionCommandContextActions {
    waitForIdle: () => Promise<void>;
    newSession: (options?: {
        parentSession?: string;
        setup?: (sessionManager: SessionManager) => Promise<void>;
        withSession?: (ctx: ReplacedSessionContext) => Promise<void>;
    }) => Promise<{
        cancelled: boolean;
    }>;
    fork: (entryId: string, options?: {
        position?: "before" | "at";
        withSession?: (ctx: ReplacedSessionContext) => Promise<void>;
    }) => Promise<{
        cancelled: boolean;
    }>;
    navigateTree: (targetId: string, options?: {
        summarize?: boolean;
        customInstructions?: string;
        replaceInstructions?: boolean;
        label?: string;
    }) => Promise<{
        cancelled: boolean;
    }>;
    switchSession: (sessionPath: string, options?: {
        withSession?: (ctx: ReplacedSessionContext) => Promise<void>;
    }) => Promise<{
        cancelled: boolean;
    }>;
    reload: () => Promise<void>;
}
/**
 * Full runtime = state + actions.
 * Created by loader with throwing action stubs, completed by runner.initialize().
 */
export interface ExtensionRuntime extends ExtensionRuntimeState, ExtensionActions {
}
/** Loaded extension with all registered items. */
export interface Extension {
    path: string;
    resolvedPath: string;
    sourceInfo: SourceInfo;
    handlers: Map<string, HandlerFn[]>;
    tools: Map<string, RegisteredTool>;
    messageRenderers: Map<string, MessageRenderer>;
    commands: Map<string, RegisteredCommand>;
    flags: Map<string, ExtensionFlag>;
    shortcuts: Map<KeyId, ExtensionShortcut>;
}
/** Result of loading extensions. */
export interface LoadExtensionsResult {
    extensions: Extension[];
    errors: Array<{
        path: string;
        error: string;
    }>;
    /** Shared runtime - actions are throwing stubs until runner.initialize() */
    runtime: ExtensionRuntime;
}
export interface ExtensionError {
    extensionPath: string;
    event: string;
    error: string;
    stack?: string;
}
//# sourceMappingURL=types.d.ts.map


**Exploring system prompts**

I'm considering accessing the getSystemPrompt in context events, but I wonder if I could append it somehow? It seems like I need to look for the context event results, but that might be tricky. I think it would be good enough to have a prompt resource along with the tool or command. So, let's go ahead and implement that! I'll just have to keep in mind all these moving parts.

write

import { execFileSync } from "node:child_process";
import path from "node:path";
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
import { Type } from "@sinclair/typebox";

const HUMAND_ROOT = "/home/sebas/work/humand";
const HUMAND_PROMPT = "/home/sebas/pi-config/humand/HUMAND.md";
const HUMAND_SETUP = "/home/sebas/pi-config/humand/workflow-setup.sh";
const HUMAND_ORGS = ["HumandDev", "sfavaron-hu"];
const NOTICE = [
	"Humand mode active.",
	"Isolated Humand workflow available.",
	"Source: pi-config/humand + hu-ai-agent-plugin clone.",
	"Use explicit Humand skills/commands only; non-Humand work stays unchanged.",
].join(" ");

function isInsideHumandTree(cwd: string): boolean {
	const resolved = path.resolve(cwd);
	return resolved === HUMAND_ROOT || resolved.startsWith(`${HUMAND_ROOT}/`);
}

function getOriginUrl(cwd: string): string {
	try {
		return execFileSync("git", ["remote", "get-url", "origin"], {
			cwd,
			encoding: "utf8",
			stdio: ["ignore", "pipe", "ignore"],
		}).trim();
	} catch {
		return "";
	}
}

function hasHumandRemote(cwd: string): boolean {
	const url = getOriginUrl(cwd);
	return HUMAND_ORGS.some((org) => url.includes(org));
}

function detectHumandMode(cwd: string): boolean {
	return isInsideHumandTree(cwd) || hasHumandRemote(cwd);
}

function getBootstrapEnv(cwd: string): Record<string, string> {
	try {
		const output = execFileSync("bash", [HUMAND_SETUP, cwd], {
			encoding: "utf8",
			stdio: ["ignore", "pipe", "ignore"],
		}).trim();
		const env: Record<string, string> = {};
		for (const line of output.split("\n")) {
			const idx = line.indexOf("=");
			if (idx <= 0) continue;
			const key = line.slice(0, idx);
			let value = line.slice(idx + 1);
			if ((value.startsWith("'") && value.endsWith("'")) || (value.startsWith('"') && value.endsWith('"'))) {
				value = value.slice(1, -1);
			}
			env[key] = value;
		}
		return env;
	} catch {
		return {};
	}
}

function describeHumandContext(cwd: string): string[] {
	const env = getBootstrapEnv(cwd);
	const inTree = isInsideHumandTree(cwd) ? "yes" : "no";
	const remote = hasHumandRemote(cwd) ? "yes" : "no";
	return [
		`cwd=${cwd}`,
		`humand_tree=${inTree}`,
		`humand_remote=${remote}`,
		`origin=${getOriginUrl(cwd) || "-"}`,
		`humand_prompt=${HUMAND_PROMPT}`,
		`HUMAND_PI_CONFIG_HOME=${env.HUMAND_PI_CONFIG_HOME || ""}`,
		`HUMAND_PI_STATE_HOME=${env.HUMAND_PI_STATE_HOME || ""}`,
		`HUMAND_PI_LOCAL_CONFIG_FILE=${env.HUMAND_PI_LOCAL_CONFIG_FILE || ""}`,
		"bootstrap=bash /home/sebas/pi-config/humand/workflow-setup.sh <workspace_root>",
	];
}

export default function humandMode(pi: ExtensionAPI) {
	let lastAnnouncedKey: string | null = null;

	const maybeAnnounce = (ctx: any, reason?: string) => {
		if (!ctx?.hasUI) return;
		if (!detectHumandMode(ctx.cwd)) return;
		const key = `${ctx.cwd}:${reason || ""}`;
		if (lastAnnouncedKey === key) return;
		lastAnnouncedKey = key;
		ctx.ui.notify(`${NOTICE} ${describeHumandContext(ctx.cwd).join(" | ")}`, "info");
	};

	pi.on("resources_discover", async (event) => {
		if (!detectHumandMode(event.cwd)) return undefined;
		return { promptPaths: [HUMAND_PROMPT] };
	});

	pi.on("session_start", async (event, ctx) => {
		if (event.reason === "reload") return;
		maybeAnnounce(ctx, event.reason);
	});

	pi.registerCommand("humand-mode", {
		description: "Show whether isolated Humand mode is active for this session",
		handler: async (_args, ctx) => {
			const active = detectHumandMode(ctx.cwd);
			const text = active
				? `${NOTICE} ${describeHumandContext(ctx.cwd).join(" | ")}`
				: `Humand mode inactive for current cwd. cwd=${ctx.cwd}`;
			ctx.ui.notify(text, active ? "info" : "warning");
		},
	});

	pi.registerTool({
		name: "humand_context",
		label: "Humand Context",
		description: "Return isolated Humand workflow activation status and resolved bootstrap variables for the current cwd",
		parameters: Type.Object({}),
		async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
			const active = detectHumandMode(ctx.cwd);
			const env = getBootstrapEnv(ctx.cwd);
			const details = {
				active,
				cwd: ctx.cwd,
				origin: getOriginUrl(ctx.cwd),
				prompt: HUMAND_PROMPT,
				env,
			};
			const lines = [
				`active=${active}`,
				`cwd=${ctx.cwd}`,
				`origin=${details.origin || "-"}`,
				`prompt=${HUMAND_PROMPT}`,
				...Object.entries(env).map(([k, v]) => `${k}=${v}`),
			];
			return {
				content: [{ type: "text", text: lines.join("\n") }],
				details,
			};
		},
	});
}


Successfully wrote 4496 bytes to /home/sebas/pi-config/extensions/humand-mode.ts


> typecheck
> tsc --noEmit


bash

cd /home/sebas/work/humand && pi --model qwen/qwen3-coder:free:minimal --no-session -p "Use the humand_context tool and print its full output only."


No API key found for cline.

Use /login to log into a provider via OAuth or API key. See:
  /home/sebas/.npm-global/lib/node_modules/@earendil-works/pi-coding-agent/docs/providers.md
  /home/sebas/.npm-global/lib/node_modules/@earendil-works/pi-coding-agent/docs/models.md

Command exited with code 1


todo

direct noninteractive pi run blocked by provider auth in shell context; switched to extension-level deterministic validation + tool-level self-checks